mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-02-22 17:02:01 +00:00
Compare commits
5 Commits
v5.0.0-alp
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff771c5c22 | ||
|
|
d7b00ffeea | ||
|
|
22c4c262ea | ||
|
|
baf4798665 | ||
|
|
bea680edec |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -39,3 +39,6 @@ __debug_*
|
|||||||
|
|
||||||
# infisical
|
# infisical
|
||||||
/.infisical.json
|
/.infisical.json
|
||||||
|
|
||||||
|
# traefik data
|
||||||
|
/traefik
|
||||||
|
|||||||
@@ -30,15 +30,9 @@ func healthcheckCmd() *cli.Command {
|
|||||||
|
|
||||||
appUrl := "http://127.0.0.1:3000"
|
appUrl := "http://127.0.0.1:3000"
|
||||||
|
|
||||||
appUrlEnv := os.Getenv("TINYAUTH_APPURL")
|
|
||||||
srvAddr := os.Getenv("TINYAUTH_SERVER_ADDRESS")
|
srvAddr := os.Getenv("TINYAUTH_SERVER_ADDRESS")
|
||||||
srvPort := os.Getenv("TINYAUTH_SERVER_PORT")
|
srvPort := os.Getenv("TINYAUTH_SERVER_PORT")
|
||||||
|
|
||||||
if appUrlEnv != "" {
|
|
||||||
appUrl = appUrlEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local-direct connection is preferred over the public app URL
|
|
||||||
if srvAddr != "" && srvPort != "" {
|
if srvAddr != "" && srvPort != "" {
|
||||||
appUrl = fmt.Sprintf("http://%s:%s", srvAddr, srvPort)
|
appUrl = fmt.Sprintf("http://%s:%s", srvAddr, srvPort)
|
||||||
}
|
}
|
||||||
@@ -48,7 +42,7 @@ func healthcheckCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if appUrl == "" {
|
if appUrl == "" {
|
||||||
return errors.New("TINYAUTH_APPURL is not set and no argument was provided")
|
return errors.New("Could not determine app URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
tlog.App.Info().Str("app_url", appUrl).Msg("Performing health check")
|
tlog.App.Info().Str("app_url", appUrl).Msg("Performing health check")
|
||||||
|
|||||||
@@ -32,34 +32,43 @@ export const ContinuePage = () => {
|
|||||||
cookieDomain,
|
cookieDomain,
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRedirect = useCallback(() => {
|
const urlHref = url?.href;
|
||||||
|
|
||||||
|
const hasValidRedirect = valid && allowedProto;
|
||||||
|
const showUntrustedWarning =
|
||||||
|
hasValidRedirect && !trusted && !disableUiWarnings;
|
||||||
|
const showInsecureWarning =
|
||||||
|
hasValidRedirect && httpsDowngrade && !disableUiWarnings;
|
||||||
|
const shouldAutoRedirect =
|
||||||
|
isLoggedIn &&
|
||||||
|
hasValidRedirect &&
|
||||||
|
!showUntrustedWarning &&
|
||||||
|
!showInsecureWarning;
|
||||||
|
|
||||||
|
const redirectToTarget = useCallback(() => {
|
||||||
|
if (!urlHref || hasRedirected.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
hasRedirected.current = true;
|
hasRedirected.current = true;
|
||||||
|
window.location.assign(urlHref);
|
||||||
|
}, [urlHref]);
|
||||||
|
|
||||||
|
const handleRedirect = useCallback(() => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
window.location.assign(url!);
|
redirectToTarget();
|
||||||
}, [url]);
|
}, [redirectToTarget]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoggedIn) {
|
if (!shouldAutoRedirect) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasRedirected.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(!valid || !allowedProto || !trusted || httpsDowngrade) &&
|
|
||||||
!disableUiWarnings
|
|
||||||
) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto = setTimeout(() => {
|
const auto = setTimeout(() => {
|
||||||
handleRedirect();
|
redirectToTarget();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
const reveal = setTimeout(() => {
|
const reveal = setTimeout(() => {
|
||||||
setIsLoading(false);
|
|
||||||
setShowRedirectButton(true);
|
setShowRedirectButton(true);
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
@@ -67,18 +76,7 @@ export const ContinuePage = () => {
|
|||||||
clearTimeout(auto);
|
clearTimeout(auto);
|
||||||
clearTimeout(reveal);
|
clearTimeout(reveal);
|
||||||
};
|
};
|
||||||
}, [
|
}, [shouldAutoRedirect, redirectToTarget]);
|
||||||
isLoggedIn,
|
|
||||||
hasRedirected,
|
|
||||||
valid,
|
|
||||||
allowedProto,
|
|
||||||
trusted,
|
|
||||||
httpsDowngrade,
|
|
||||||
disableUiWarnings,
|
|
||||||
setIsLoading,
|
|
||||||
handleRedirect,
|
|
||||||
setShowRedirectButton,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
return (
|
return (
|
||||||
@@ -89,11 +87,11 @@ export const ContinuePage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid || !allowedProto) {
|
if (!hasValidRedirect) {
|
||||||
return <Navigate to="/logout" replace />;
|
return <Navigate to="/logout" replace />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!trusted && !disableUiWarnings) {
|
if (showUntrustedWarning) {
|
||||||
return (
|
return (
|
||||||
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -113,7 +111,7 @@ export const ContinuePage = () => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleRedirect()}
|
onClick={handleRedirect}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
>
|
>
|
||||||
@@ -131,7 +129,7 @@ export const ContinuePage = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpsDowngrade && !disableUiWarnings) {
|
if (showInsecureWarning) {
|
||||||
return (
|
return (
|
||||||
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
<Card role="alert" aria-live="assertive" className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@@ -150,7 +148,7 @@ export const ContinuePage = () => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter className="flex flex-col items-stretch gap-2">
|
<CardFooter className="flex flex-col items-stretch gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleRedirect()}
|
onClick={handleRedirect}
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
variant="warning"
|
variant="warning"
|
||||||
>
|
>
|
||||||
@@ -178,7 +176,7 @@ export const ContinuePage = () => {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
{showRedirectButton && (
|
{showRedirectButton && (
|
||||||
<CardFooter className="flex flex-col items-stretch">
|
<CardFooter className="flex flex-col items-stretch">
|
||||||
<Button onClick={() => handleRedirect()}>
|
<Button onClick={handleRedirect}>
|
||||||
{t("continueRedirectManually")}
|
{t("continueRedirectManually")}
|
||||||
</Button>
|
</Button>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ type OIDCClientConfig struct {
|
|||||||
ClientID string `description:"OIDC client ID." yaml:"clientId"`
|
ClientID string `description:"OIDC client ID." yaml:"clientId"`
|
||||||
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret"`
|
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret"`
|
||||||
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile"`
|
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile"`
|
||||||
TrustedRedirectURIs []string `description:"List of trusted redirect URLs." yaml:"trustedRedirectUrls"`
|
TrustedRedirectURIs []string `description:"List of trusted redirect URIs." yaml:"trustedRedirectUris"`
|
||||||
Name string `description:"Client name in UI." yaml:"name"`
|
Name string `description:"Client name in UI." yaml:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ type TokenRequest struct {
|
|||||||
Code string `form:"code" url:"code"`
|
Code string `form:"code" url:"code"`
|
||||||
RedirectURI string `form:"redirect_uri" url:"redirect_uri"`
|
RedirectURI string `form:"redirect_uri" url:"redirect_uri"`
|
||||||
RefreshToken string `form:"refresh_token" url:"refresh_token"`
|
RefreshToken string `form:"refresh_token" url:"refresh_token"`
|
||||||
|
ClientSecret string `form:"client_secret" url:"client_secret"`
|
||||||
|
ClientID string `form:"client_id" url:"client_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackError struct {
|
type CallbackError struct {
|
||||||
@@ -49,6 +51,11 @@ type ClientRequest struct {
|
|||||||
ClientID string `uri:"id" binding:"required"`
|
ClientID string `uri:"id" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientCredentials struct {
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
}
|
||||||
|
|
||||||
func NewOIDCController(config OIDCControllerConfig, oidcService *service.OIDCService, router *gin.RouterGroup) *OIDCController {
|
func NewOIDCController(config OIDCControllerConfig, oidcService *service.OIDCService, router *gin.RouterGroup) *OIDCController {
|
||||||
return &OIDCController{
|
return &OIDCController{
|
||||||
config: config,
|
config: config,
|
||||||
@@ -210,29 +217,45 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rclientId, rclientSecret, ok := c.Request.BasicAuth()
|
// First we try form values
|
||||||
|
creds := ClientCredentials{
|
||||||
if !ok {
|
ClientID: req.ClientID,
|
||||||
tlog.App.Error().Msg("Missing authorization header")
|
ClientSecret: req.ClientSecret,
|
||||||
c.Header("www-authenticate", "basic")
|
|
||||||
c.JSON(401, gin.H{
|
|
||||||
"error": "invalid_client",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client, ok := controller.oidc.GetClient(rclientId)
|
// If it fails, we try basic auth
|
||||||
|
if creds.ClientID == "" || creds.ClientSecret == "" {
|
||||||
|
tlog.App.Debug().Msg("Tried form values and they are empty, trying basic auth")
|
||||||
|
|
||||||
|
clientId, clientSecret, ok := c.Request.BasicAuth()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
tlog.App.Error().Msg("Missing authorization header")
|
||||||
|
c.Header("www-authenticate", "basic")
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"error": "invalid_client",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
creds.ClientID = clientId
|
||||||
|
creds.ClientSecret = clientSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
// END - we don't support other authentication methods
|
||||||
|
|
||||||
|
client, ok := controller.oidc.GetClient(creds.ClientID)
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
tlog.App.Warn().Str("client_id", rclientId).Msg("Client not found")
|
tlog.App.Warn().Str("client_id", creds.ClientID).Msg("Client not found")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, gin.H{
|
||||||
"error": "invalid_client",
|
"error": "invalid_client",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.ClientSecret != rclientSecret {
|
if client.ClientSecret != creds.ClientSecret {
|
||||||
tlog.App.Warn().Str("client_id", rclientId).Msg("Invalid client secret")
|
tlog.App.Warn().Str("client_id", creds.ClientID).Msg("Invalid client secret")
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, gin.H{
|
||||||
"error": "invalid_client",
|
"error": "invalid_client",
|
||||||
})
|
})
|
||||||
@@ -286,7 +309,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
|
|
||||||
tokenResponse = tokenRes
|
tokenResponse = tokenRes
|
||||||
case "refresh_token":
|
case "refresh_token":
|
||||||
tokenRes, err := controller.oidc.RefreshAccessToken(c, req.RefreshToken, rclientId)
|
tokenRes, err := controller.oidc.RefreshAccessToken(c, req.RefreshToken, creds.ClientID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, service.ErrTokenExpired) {
|
if errors.Is(err, service.ErrTokenExpired) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (controller *WellKnownController) OpenIDConnectConfiguration(c *gin.Context
|
|||||||
GrantTypesSupported: service.SupportedGrantTypes,
|
GrantTypesSupported: service.SupportedGrantTypes,
|
||||||
SubjectTypesSupported: []string{"pairwise"},
|
SubjectTypesSupported: []string{"pairwise"},
|
||||||
IDTokenSigningAlgValuesSupported: []string{"RS256"},
|
IDTokenSigningAlgValuesSupported: []string{"RS256"},
|
||||||
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic"},
|
TokenEndpointAuthMethodsSupported: []string{"client_secret_basic", "client_secret_post"},
|
||||||
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "groups"},
|
ClaimsSupported: []string{"sub", "updated_at", "name", "preferred_username", "email", "groups"},
|
||||||
ServiceDocumentation: "https://tinyauth.app/docs/reference/openid",
|
ServiceDocumentation: "https://tinyauth.app/docs/reference/openid",
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user