mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-03-04 05:42:01 +00:00
Compare commits
8 Commits
v5.0.0-rc.
...
feat/oidc-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec8121499c | ||
|
|
0e6bcf9713 | ||
|
|
af5a8bc452 | ||
|
|
de980815ce | ||
|
|
189ad7115a | ||
|
|
2f2556d480 | ||
|
|
f1512f45b7 | ||
|
|
cd410b6cdf |
31
.env.example
31
.env.example
@@ -4,14 +4,23 @@
|
|||||||
|
|
||||||
# The base URL where the app is hosted.
|
# The base URL where the app is hosted.
|
||||||
TINYAUTH_APPURL=
|
TINYAUTH_APPURL=
|
||||||
|
|
||||||
|
# database config
|
||||||
|
|
||||||
|
# The path to the database, including file name.
|
||||||
|
TINYAUTH_DATABASE_PATH="./tinyauth.db"
|
||||||
|
|
||||||
|
# analytics config
|
||||||
|
|
||||||
|
# Enable periodic version information collection.
|
||||||
|
TINYAUTH_ANALYTICS_ENABLED=true
|
||||||
|
|
||||||
|
# resources config
|
||||||
|
|
||||||
|
# Enable the resources server.
|
||||||
|
TINYAUTH_RESOURCES_ENABLED=true
|
||||||
# The directory where resources are stored.
|
# The directory where resources are stored.
|
||||||
TINYAUTH_RESOURCESDIR="./resources"
|
TINYAUTH_RESOURCES_PATH="./resources"
|
||||||
# The path to the database file.
|
|
||||||
TINYAUTH_DATABASEPATH="./tinyauth.db"
|
|
||||||
# Disable analytics.
|
|
||||||
TINYAUTH_DISABLEANALYTICS=false
|
|
||||||
# Disable resources server.
|
|
||||||
TINYAUTH_DISABLERESOURCES=false
|
|
||||||
|
|
||||||
# server config
|
# server config
|
||||||
|
|
||||||
@@ -107,9 +116,9 @@ TINYAUTH_OAUTH_PROVIDERS_name_NAME=
|
|||||||
|
|
||||||
# oidc config
|
# oidc config
|
||||||
|
|
||||||
# Path to the private key file.
|
# Path to the private key file, including file name.
|
||||||
TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key"
|
TINYAUTH_OIDC_PRIVATEKEYPATH="./tinyauth_oidc_key"
|
||||||
# Path to the public key file.
|
# Path to the public key file, including file name.
|
||||||
TINYAUTH_OIDC_PUBLICKEYPATH="./tinyauth_oidc_key.pub"
|
TINYAUTH_OIDC_PUBLICKEYPATH="./tinyauth_oidc_key.pub"
|
||||||
# OIDC client ID.
|
# OIDC client ID.
|
||||||
TINYAUTH_OIDC_CLIENTS_name_CLIENTID=
|
TINYAUTH_OIDC_CLIENTS_name_CLIENTID=
|
||||||
@@ -130,8 +139,8 @@ TINYAUTH_UI_TITLE="Tinyauth"
|
|||||||
TINYAUTH_UI_FORGOTPASSWORDMESSAGE="You can change your password by changing the configuration."
|
TINYAUTH_UI_FORGOTPASSWORDMESSAGE="You can change your password by changing the configuration."
|
||||||
# Path to the background image.
|
# Path to the background image.
|
||||||
TINYAUTH_UI_BACKGROUNDIMAGE="/background.jpg"
|
TINYAUTH_UI_BACKGROUNDIMAGE="/background.jpg"
|
||||||
# Disable UI warnings.
|
# Enable UI warnings.
|
||||||
TINYAUTH_UI_DISABLEWARNINGS=false
|
TINYAUTH_UI_WARNINGSENABLED=true
|
||||||
|
|
||||||
# ldap config
|
# ldap config
|
||||||
|
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -45,3 +45,6 @@ __debug_*
|
|||||||
|
|
||||||
# generated markdown (for docs)
|
# generated markdown (for docs)
|
||||||
/config.gen.md
|
/config.gen.md
|
||||||
|
|
||||||
|
# testing config
|
||||||
|
config.certify.yml
|
||||||
|
|||||||
@@ -57,9 +57,9 @@ EXPOSE 3000
|
|||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db
|
ENV TINYAUTH_DATABASE_PATH=/data/tinyauth.db
|
||||||
|
|
||||||
ENV TINYAUTH_RESOURCESDIR=/data/resources
|
ENV TINYAUTH_RESOURCES_PATH=/data/resources
|
||||||
|
|
||||||
ENV PATH=$PATH:/tinyauth
|
ENV PATH=$PATH:/tinyauth
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ COPY ./air.toml ./
|
|||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db
|
ENV TINYAUTH_DATABASE_PATH=/data/tinyauth.db
|
||||||
|
|
||||||
ENV TINYAUTH_RESOURCESDIR=/data/resources
|
ENV TINYAUTH_RESOURCES_PATH=/data/resources
|
||||||
|
|
||||||
ENTRYPOINT ["air", "-c", "air.toml"]
|
ENTRYPOINT ["air", "-c", "air.toml"]
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ EXPOSE 3000
|
|||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
ENV TINYAUTH_DATABASEPATH=/data/tinyauth.db
|
ENV TINYAUTH_DATABASE_PATH=/data/tinyauth.db
|
||||||
|
|
||||||
ENV TINYAUTH_RESOURCESDIR=/data/resources
|
ENV TINYAUTH_RESOURCES_PATH=/data/resources
|
||||||
|
|
||||||
ENV PATH=$PATH:/tinyauth
|
ENV PATH=$PATH:/tinyauth
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const BaseLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Layout = () => {
|
export const Layout = () => {
|
||||||
const { appUrl, disableUiWarnings } = useAppContext();
|
const { appUrl, warningsEnabled } = useAppContext();
|
||||||
const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => {
|
const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(() => {
|
||||||
return window.sessionStorage.getItem("ignoreDomainWarning") === "true";
|
return window.sessionStorage.getItem("ignoreDomainWarning") === "true";
|
||||||
});
|
});
|
||||||
@@ -42,7 +42,7 @@ export const Layout = () => {
|
|||||||
setIgnoreDomainWarning(true);
|
setIgnoreDomainWarning(true);
|
||||||
}, [setIgnoreDomainWarning]);
|
}, [setIgnoreDomainWarning]);
|
||||||
|
|
||||||
if (!ignoreDomainWarning && !disableUiWarnings && appUrl !== currentUrl) {
|
if (!ignoreDomainWarning && warningsEnabled && appUrl !== currentUrl) {
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
<DomainWarning
|
<DomainWarning
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export type OIDCValues = {
|
|||||||
client_id: string;
|
client_id: string;
|
||||||
redirect_uri: string;
|
redirect_uri: string;
|
||||||
state: string;
|
state: string;
|
||||||
|
nonce: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IuseOIDCParams {
|
interface IuseOIDCParams {
|
||||||
@@ -13,7 +14,7 @@ interface IuseOIDCParams {
|
|||||||
missingParams: string[];
|
missingParams: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const optionalParams: string[] = ["state"];
|
const optionalParams: string[] = ["state", "nonce"];
|
||||||
|
|
||||||
export function useOIDCParams(params: URLSearchParams): IuseOIDCParams {
|
export function useOIDCParams(params: URLSearchParams): IuseOIDCParams {
|
||||||
let compiled: string = "";
|
let compiled: string = "";
|
||||||
@@ -26,6 +27,7 @@ export function useOIDCParams(params: URLSearchParams): IuseOIDCParams {
|
|||||||
client_id: params.get("client_id") ?? "",
|
client_id: params.get("client_id") ?? "",
|
||||||
redirect_uri: params.get("redirect_uri") ?? "",
|
redirect_uri: params.get("redirect_uri") ?? "",
|
||||||
state: params.get("state") ?? "",
|
state: params.get("state") ?? "",
|
||||||
|
nonce: params.get("nonce") ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const key of Object.keys(values)) {
|
for (const key of Object.keys(values)) {
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
"invalidInput": "Неисправан унос",
|
"invalidInput": "Неисправан унос",
|
||||||
"domainWarningTitle": "Неисправан домен",
|
"domainWarningTitle": "Неисправан домен",
|
||||||
"domainWarningSubtitle": "Ова инстанца је подешена да јој се приступа са <code>{{appUrl}}</code>, али се користи <code>{{currentUrl}}</code>. Ако наставите, можете искусити проблеме са аутентификацијом.",
|
"domainWarningSubtitle": "Ова инстанца је подешена да јој се приступа са <code>{{appUrl}}</code>, али се користи <code>{{currentUrl}}</code>. Ако наставите, можете искусити проблеме са аутентификацијом.",
|
||||||
"domainWarningCurrent": "Current:",
|
"domainWarningCurrent": "Тренутни:",
|
||||||
"domainWarningExpected": "Expected:",
|
"domainWarningExpected": "Очекивани:",
|
||||||
"ignoreTitle": "Игнориши",
|
"ignoreTitle": "Игнориши",
|
||||||
"goToCorrectDomainTitle": "Иди на исправан домен",
|
"goToCorrectDomainTitle": "Иди на исправан домен",
|
||||||
"authorizeTitle": "Ауторизуј",
|
"authorizeTitle": "Ауторизуј",
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ export const AuthorizePage = () => {
|
|||||||
client_id: props.client_id,
|
client_id: props.client_id,
|
||||||
redirect_uri: props.redirect_uri,
|
redirect_uri: props.redirect_uri,
|
||||||
state: props.state,
|
state: props.state,
|
||||||
|
nonce: props.nonce,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
mutationKey: ["authorize", props.client_id],
|
mutationKey: ["authorize", props.client_id],
|
||||||
@@ -155,8 +156,8 @@ export const AuthorizePage = () => {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="mb-2">
|
<CardHeader className="mb-2">
|
||||||
<div className="flex flex-col gap-3 items-center justify-center text-center">
|
<div className="flex flex-col gap-3 items-center justify-center text-center">
|
||||||
<div className="bg-accent-foreground box-content text-muted text-xl font-bold font-sans rounded-lg size-10 p-2 flex items-center justify-center">
|
<div className="bg-accent-foreground box-content text-muted text-xl font-bold font-sans rounded-lg size-8 p-2 flex items-center justify-center">
|
||||||
{getClientInfo.data?.name.slice(0, 1)}
|
{getClientInfo.data?.name.slice(0, 1) || "U"}
|
||||||
</div>
|
</div>
|
||||||
<CardTitle className="text-xl">
|
<CardTitle className="text-xl">
|
||||||
{t("authorizeCardTitle", {
|
{t("authorizeCardTitle", {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|||||||
import { useRedirectUri } from "@/lib/hooks/redirect-uri";
|
import { useRedirectUri } from "@/lib/hooks/redirect-uri";
|
||||||
|
|
||||||
export const ContinuePage = () => {
|
export const ContinuePage = () => {
|
||||||
const { cookieDomain, disableUiWarnings } = useAppContext();
|
const { cookieDomain, warningsEnabled } = useAppContext();
|
||||||
const { isLoggedIn } = useUserContext();
|
const { isLoggedIn } = useUserContext();
|
||||||
const { search } = useLocation();
|
const { search } = useLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -35,10 +35,9 @@ export const ContinuePage = () => {
|
|||||||
const urlHref = url?.href;
|
const urlHref = url?.href;
|
||||||
|
|
||||||
const hasValidRedirect = valid && allowedProto;
|
const hasValidRedirect = valid && allowedProto;
|
||||||
const showUntrustedWarning =
|
const showUntrustedWarning = hasValidRedirect && !trusted && warningsEnabled;
|
||||||
hasValidRedirect && !trusted && !disableUiWarnings;
|
|
||||||
const showInsecureWarning =
|
const showInsecureWarning =
|
||||||
hasValidRedirect && httpsDowngrade && !disableUiWarnings;
|
hasValidRedirect && httpsDowngrade && warningsEnabled;
|
||||||
const shouldAutoRedirect =
|
const shouldAutoRedirect =
|
||||||
isLoggedIn &&
|
isLoggedIn &&
|
||||||
hasValidRedirect &&
|
hasValidRedirect &&
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const appContextSchema = z.object({
|
|||||||
forgotPasswordMessage: z.string(),
|
forgotPasswordMessage: z.string(),
|
||||||
backgroundImage: z.string(),
|
backgroundImage: z.string(),
|
||||||
oauthAutoRedirect: z.string(),
|
oauthAutoRedirect: z.string(),
|
||||||
disableUiWarnings: z.boolean(),
|
warningsEnabled: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
export type AppContextSchema = z.infer<typeof appContextSchema>;
|
||||||
|
|||||||
2
internal/assets/migrations/000006_oidc_nonce.down.sql
Normal file
2
internal/assets/migrations/000006_oidc_nonce.down.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "oidc_codes" DROP COLUMN "nonce";
|
||||||
|
ALTER TABLE "oidc_tokens" DROP COLUMN "nonce";
|
||||||
2
internal/assets/migrations/000006_oidc_nonce.up.sql
Normal file
2
internal/assets/migrations/000006_oidc_nonce.up.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "oidc_codes" ADD COLUMN "nonce" TEXT DEFAULT "";
|
||||||
|
ALTER TABLE "oidc_tokens" ADD COLUMN "nonce" TEXT DEFAULT "";
|
||||||
@@ -124,7 +124,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
tlog.App.Trace().Str("redirectCookieName", app.context.redirectCookieName).Msg("Redirect cookie name")
|
tlog.App.Trace().Str("redirectCookieName", app.context.redirectCookieName).Msg("Redirect cookie name")
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
db, err := app.SetupDatabase(app.config.DatabasePath)
|
db, err := app.SetupDatabase(app.config.Database.Path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to setup database: %w", err)
|
return fmt.Errorf("failed to setup database: %w", err)
|
||||||
@@ -193,7 +193,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
go app.dbCleanup(queries)
|
go app.dbCleanup(queries)
|
||||||
|
|
||||||
// If analytics are not disabled, start heartbeat
|
// If analytics are not disabled, start heartbeat
|
||||||
if !app.config.DisableAnalytics {
|
if app.config.Analytics.Enabled {
|
||||||
tlog.App.Debug().Msg("Starting heartbeat routine")
|
tlog.App.Debug().Msg("Starting heartbeat routine")
|
||||||
go app.heartbeat()
|
go app.heartbeat()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
|
|||||||
ForgotPasswordMessage: app.config.UI.ForgotPasswordMessage,
|
ForgotPasswordMessage: app.config.UI.ForgotPasswordMessage,
|
||||||
BackgroundImage: app.config.UI.BackgroundImage,
|
BackgroundImage: app.config.UI.BackgroundImage,
|
||||||
OAuthAutoRedirect: app.config.OAuth.AutoRedirect,
|
OAuthAutoRedirect: app.config.OAuth.AutoRedirect,
|
||||||
DisableUIWarnings: app.config.UI.DisableWarnings,
|
WarningsEnabled: app.config.UI.WarningsEnabled,
|
||||||
}, apiRouter)
|
}, apiRouter)
|
||||||
|
|
||||||
contextController.SetupRoutes()
|
contextController.SetupRoutes()
|
||||||
@@ -103,8 +103,8 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
|
|||||||
userController.SetupRoutes()
|
userController.SetupRoutes()
|
||||||
|
|
||||||
resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{
|
resourcesController := controller.NewResourcesController(controller.ResourcesControllerConfig{
|
||||||
ResourcesDir: app.config.ResourcesDir,
|
Path: app.config.Resources.Path,
|
||||||
ResourcesDisabled: app.config.DisableResources,
|
Enabled: app.config.Resources.Enabled,
|
||||||
}, &engine.RouterGroup)
|
}, &engine.RouterGroup)
|
||||||
|
|
||||||
resourcesController.SetupRoutes()
|
resourcesController.SetupRoutes()
|
||||||
|
|||||||
@@ -3,8 +3,16 @@ package config
|
|||||||
// Default configuration
|
// Default configuration
|
||||||
func NewDefaultConfiguration() *Config {
|
func NewDefaultConfiguration() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
ResourcesDir: "./resources",
|
Database: DatabaseConfig{
|
||||||
DatabasePath: "./tinyauth.db",
|
Path: "./tinyauth.db",
|
||||||
|
},
|
||||||
|
Analytics: AnalyticsConfig{
|
||||||
|
Enabled: true,
|
||||||
|
},
|
||||||
|
Resources: ResourcesConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Path: "./resources",
|
||||||
|
},
|
||||||
Server: ServerConfig{
|
Server: ServerConfig{
|
||||||
Port: 3000,
|
Port: 3000,
|
||||||
Address: "0.0.0.0",
|
Address: "0.0.0.0",
|
||||||
@@ -19,6 +27,7 @@ func NewDefaultConfiguration() *Config {
|
|||||||
Title: "Tinyauth",
|
Title: "Tinyauth",
|
||||||
ForgotPasswordMessage: "You can change your password by changing the configuration.",
|
ForgotPasswordMessage: "You can change your password by changing the configuration.",
|
||||||
BackgroundImage: "/background.jpg",
|
BackgroundImage: "/background.jpg",
|
||||||
|
WarningsEnabled: true,
|
||||||
},
|
},
|
||||||
Ldap: LdapConfig{
|
Ldap: LdapConfig{
|
||||||
Insecure: false,
|
Insecure: false,
|
||||||
@@ -68,20 +77,32 @@ var RedirectCookieName = "tinyauth-redirect"
|
|||||||
// Main app config
|
// Main app config
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"`
|
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"`
|
||||||
ResourcesDir string `description:"The directory where resources are stored." yaml:"resourcesDir"`
|
Database DatabaseConfig `description:"Database configuration." yaml:"database"`
|
||||||
DatabasePath string `description:"The path to the database file." yaml:"databasePath"`
|
Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics"`
|
||||||
DisableAnalytics bool `description:"Disable analytics." yaml:"disableAnalytics"`
|
Resources ResourcesConfig `description:"Resources configuration." yaml:"resources"`
|
||||||
DisableResources bool `description:"Disable resources server." yaml:"disableResources"`
|
Server ServerConfig `description:"Server configuration." yaml:"server"`
|
||||||
Server ServerConfig `description:"Server configuration." yaml:"server"`
|
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
|
||||||
Auth AuthConfig `description:"Authentication configuration." yaml:"auth"`
|
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
|
||||||
Apps map[string]App `description:"Application ACLs configuration." yaml:"apps"`
|
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
|
||||||
OAuth OAuthConfig `description:"OAuth configuration." yaml:"oauth"`
|
OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc"`
|
||||||
OIDC OIDCConfig `description:"OIDC configuration." yaml:"oidc"`
|
UI UIConfig `description:"UI customization." yaml:"ui"`
|
||||||
UI UIConfig `description:"UI customization." yaml:"ui"`
|
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
|
||||||
Ldap LdapConfig `description:"LDAP configuration." yaml:"ldap"`
|
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
||||||
Experimental ExperimentalConfig `description:"Experimental features, use with caution." yaml:"experimental"`
|
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
||||||
Log LogConfig `description:"Logging configuration." yaml:"log"`
|
}
|
||||||
|
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
Path string `description:"The path to the database, including file name." yaml:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnalyticsConfig struct {
|
||||||
|
Enabled bool `description:"Enable periodic version information collection." yaml:"enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResourcesConfig struct {
|
||||||
|
Enabled bool `description:"Enable the resources server." yaml:"enabled"`
|
||||||
|
Path string `description:"The directory where resources are stored." yaml:"path"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
@@ -114,8 +135,8 @@ type OAuthConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OIDCConfig struct {
|
type OIDCConfig struct {
|
||||||
PrivateKeyPath string `description:"Path to the private key file." yaml:"privateKeyPath"`
|
PrivateKeyPath string `description:"Path to the private key file, including file name." yaml:"privateKeyPath"`
|
||||||
PublicKeyPath string `description:"Path to the public key file." yaml:"publicKeyPath"`
|
PublicKeyPath string `description:"Path to the public key file, including file name." yaml:"publicKeyPath"`
|
||||||
Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients"`
|
Clients map[string]OIDCClientConfig `description:"OIDC clients configuration." yaml:"clients"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +144,7 @@ type UIConfig struct {
|
|||||||
Title string `description:"The title of the UI." yaml:"title"`
|
Title string `description:"The title of the UI." yaml:"title"`
|
||||||
ForgotPasswordMessage string `description:"Message displayed on the forgot password page." yaml:"forgotPasswordMessage"`
|
ForgotPasswordMessage string `description:"Message displayed on the forgot password page." yaml:"forgotPasswordMessage"`
|
||||||
BackgroundImage string `description:"Path to the background image." yaml:"backgroundImage"`
|
BackgroundImage string `description:"Path to the background image." yaml:"backgroundImage"`
|
||||||
DisableWarnings bool `description:"Disable UI warnings." yaml:"disableWarnings"`
|
WarningsEnabled bool `description:"Enable UI warnings." yaml:"warningsEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LdapConfig struct {
|
type LdapConfig struct {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type AppContextResponse struct {
|
|||||||
ForgotPasswordMessage string `json:"forgotPasswordMessage"`
|
ForgotPasswordMessage string `json:"forgotPasswordMessage"`
|
||||||
BackgroundImage string `json:"backgroundImage"`
|
BackgroundImage string `json:"backgroundImage"`
|
||||||
OAuthAutoRedirect string `json:"oauthAutoRedirect"`
|
OAuthAutoRedirect string `json:"oauthAutoRedirect"`
|
||||||
DisableUIWarnings bool `json:"disableUiWarnings"`
|
WarningsEnabled bool `json:"warningsEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
||||||
@@ -50,7 +50,7 @@ type ContextControllerConfig struct {
|
|||||||
ForgotPasswordMessage string
|
ForgotPasswordMessage string
|
||||||
BackgroundImage string
|
BackgroundImage string
|
||||||
OAuthAutoRedirect string
|
OAuthAutoRedirect string
|
||||||
DisableUIWarnings bool
|
WarningsEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContextController struct {
|
type ContextController struct {
|
||||||
@@ -59,7 +59,7 @@ type ContextController struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewContextController(config ContextControllerConfig, router *gin.RouterGroup) *ContextController {
|
func NewContextController(config ContextControllerConfig, router *gin.RouterGroup) *ContextController {
|
||||||
if config.DisableUIWarnings {
|
if !config.WarningsEnabled {
|
||||||
tlog.App.Warn().Msg("UI warnings are disabled. This may expose users to security risks. Proceed with caution.")
|
tlog.App.Warn().Msg("UI warnings are disabled. This may expose users to security risks. Proceed with caution.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,6 +124,6 @@ func (controller *ContextController) appContextHandler(c *gin.Context) {
|
|||||||
ForgotPasswordMessage: controller.config.ForgotPasswordMessage,
|
ForgotPasswordMessage: controller.config.ForgotPasswordMessage,
|
||||||
BackgroundImage: controller.config.BackgroundImage,
|
BackgroundImage: controller.config.BackgroundImage,
|
||||||
OAuthAutoRedirect: controller.config.OAuthAutoRedirect,
|
OAuthAutoRedirect: controller.config.OAuthAutoRedirect,
|
||||||
DisableUIWarnings: controller.config.DisableUIWarnings,
|
WarningsEnabled: controller.config.WarningsEnabled,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ var contextControllerCfg = controller.ContextControllerConfig{
|
|||||||
ForgotPasswordMessage: "Contact admin to reset your password.",
|
ForgotPasswordMessage: "Contact admin to reset your password.",
|
||||||
BackgroundImage: "/assets/bg.jpg",
|
BackgroundImage: "/assets/bg.jpg",
|
||||||
OAuthAutoRedirect: "google",
|
OAuthAutoRedirect: "google",
|
||||||
DisableUIWarnings: false,
|
WarningsEnabled: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var contextCtrlTestContext = config.UserContext{
|
var contextCtrlTestContext = config.UserContext{
|
||||||
@@ -82,7 +82,7 @@ func TestAppContextHandler(t *testing.T) {
|
|||||||
ForgotPasswordMessage: contextControllerCfg.ForgotPasswordMessage,
|
ForgotPasswordMessage: contextControllerCfg.ForgotPasswordMessage,
|
||||||
BackgroundImage: contextControllerCfg.BackgroundImage,
|
BackgroundImage: contextControllerCfg.BackgroundImage,
|
||||||
OAuthAutoRedirect: contextControllerCfg.OAuthAutoRedirect,
|
OAuthAutoRedirect: contextControllerCfg.OAuthAutoRedirect,
|
||||||
DisableUIWarnings: contextControllerCfg.DisableUIWarnings,
|
WarningsEnabled: contextControllerCfg.WarningsEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
router, recorder := setupContextController(nil)
|
router, recorder := setupContextController(nil)
|
||||||
|
|||||||
@@ -296,7 +296,7 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenRes, err := controller.oidc.GenerateAccessToken(c, client, entry.Sub, entry.Scope)
|
tokenRes, err := controller.oidc.GenerateAccessToken(c, client, entry)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.App.Error().Err(err).Msg("Failed to generate access token")
|
tlog.App.Error().Err(err).Msg("Failed to generate access token")
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ResourcesControllerConfig struct {
|
type ResourcesControllerConfig struct {
|
||||||
ResourcesDir string
|
Path string
|
||||||
ResourcesDisabled bool
|
Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResourcesController struct {
|
type ResourcesController struct {
|
||||||
@@ -18,7 +18,7 @@ type ResourcesController struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewResourcesController(config ResourcesControllerConfig, router *gin.RouterGroup) *ResourcesController {
|
func NewResourcesController(config ResourcesControllerConfig, router *gin.RouterGroup) *ResourcesController {
|
||||||
fileServer := http.StripPrefix("/resources", http.FileServer(http.Dir(config.ResourcesDir)))
|
fileServer := http.StripPrefix("/resources", http.FileServer(http.Dir(config.Path)))
|
||||||
|
|
||||||
return &ResourcesController{
|
return &ResourcesController{
|
||||||
config: config,
|
config: config,
|
||||||
@@ -32,14 +32,14 @@ func (controller *ResourcesController) SetupRoutes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (controller *ResourcesController) resourcesHandler(c *gin.Context) {
|
func (controller *ResourcesController) resourcesHandler(c *gin.Context) {
|
||||||
if controller.config.ResourcesDir == "" {
|
if controller.config.Path == "" {
|
||||||
c.JSON(404, gin.H{
|
c.JSON(404, gin.H{
|
||||||
"status": 404,
|
"status": 404,
|
||||||
"message": "Resources not found",
|
"message": "Resources not found",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if controller.config.ResourcesDisabled {
|
if !controller.config.Enabled {
|
||||||
c.JSON(403, gin.H{
|
c.JSON(403, gin.H{
|
||||||
"status": 403,
|
"status": 403,
|
||||||
"message": "Resources are disabled",
|
"message": "Resources are disabled",
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ func TestResourcesHandler(t *testing.T) {
|
|||||||
group := router.Group("/")
|
group := router.Group("/")
|
||||||
|
|
||||||
ctrl := controller.NewResourcesController(controller.ResourcesControllerConfig{
|
ctrl := controller.NewResourcesController(controller.ResourcesControllerConfig{
|
||||||
ResourcesDir: "/tmp/tinyauth",
|
Path: "/tmp/tinyauth",
|
||||||
|
Enabled: true,
|
||||||
}, group)
|
}, group)
|
||||||
ctrl.SetupRoutes()
|
ctrl.SetupRoutes()
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type OidcCode struct {
|
|||||||
RedirectURI string
|
RedirectURI string
|
||||||
ClientID string
|
ClientID string
|
||||||
ExpiresAt int64
|
ExpiresAt int64
|
||||||
|
Nonce string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OidcToken struct {
|
type OidcToken struct {
|
||||||
@@ -21,6 +22,7 @@ type OidcToken struct {
|
|||||||
ClientID string
|
ClientID string
|
||||||
TokenExpiresAt int64
|
TokenExpiresAt int64
|
||||||
RefreshTokenExpiresAt int64
|
RefreshTokenExpiresAt int64
|
||||||
|
Nonce string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OidcUserinfo struct {
|
type OidcUserinfo struct {
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ INSERT INTO "oidc_codes" (
|
|||||||
"scope",
|
"scope",
|
||||||
"redirect_uri",
|
"redirect_uri",
|
||||||
"client_id",
|
"client_id",
|
||||||
"expires_at"
|
"expires_at",
|
||||||
|
"nonce"
|
||||||
) VALUES (
|
) VALUES (
|
||||||
?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?
|
||||||
)
|
)
|
||||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateOidcCodeParams struct {
|
type CreateOidcCodeParams struct {
|
||||||
@@ -30,6 +31,7 @@ type CreateOidcCodeParams struct {
|
|||||||
RedirectURI string
|
RedirectURI string
|
||||||
ClientID string
|
ClientID string
|
||||||
ExpiresAt int64
|
ExpiresAt int64
|
||||||
|
Nonce string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams) (OidcCode, error) {
|
func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams) (OidcCode, error) {
|
||||||
@@ -40,6 +42,7 @@ func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams)
|
|||||||
arg.RedirectURI,
|
arg.RedirectURI,
|
||||||
arg.ClientID,
|
arg.ClientID,
|
||||||
arg.ExpiresAt,
|
arg.ExpiresAt,
|
||||||
|
arg.Nonce,
|
||||||
)
|
)
|
||||||
var i OidcCode
|
var i OidcCode
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@@ -49,6 +52,7 @@ func (q *Queries) CreateOidcCode(ctx context.Context, arg CreateOidcCodeParams)
|
|||||||
&i.RedirectURI,
|
&i.RedirectURI,
|
||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.ExpiresAt,
|
&i.ExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -61,11 +65,12 @@ INSERT INTO "oidc_tokens" (
|
|||||||
"scope",
|
"scope",
|
||||||
"client_id",
|
"client_id",
|
||||||
"token_expires_at",
|
"token_expires_at",
|
||||||
"refresh_token_expires_at"
|
"refresh_token_expires_at",
|
||||||
|
"nonce"
|
||||||
) VALUES (
|
) VALUES (
|
||||||
?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?
|
||||||
)
|
)
|
||||||
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at
|
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateOidcTokenParams struct {
|
type CreateOidcTokenParams struct {
|
||||||
@@ -76,6 +81,7 @@ type CreateOidcTokenParams struct {
|
|||||||
ClientID string
|
ClientID string
|
||||||
TokenExpiresAt int64
|
TokenExpiresAt int64
|
||||||
RefreshTokenExpiresAt int64
|
RefreshTokenExpiresAt int64
|
||||||
|
Nonce string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateOidcToken(ctx context.Context, arg CreateOidcTokenParams) (OidcToken, error) {
|
func (q *Queries) CreateOidcToken(ctx context.Context, arg CreateOidcTokenParams) (OidcToken, error) {
|
||||||
@@ -87,6 +93,7 @@ func (q *Queries) CreateOidcToken(ctx context.Context, arg CreateOidcTokenParams
|
|||||||
arg.ClientID,
|
arg.ClientID,
|
||||||
arg.TokenExpiresAt,
|
arg.TokenExpiresAt,
|
||||||
arg.RefreshTokenExpiresAt,
|
arg.RefreshTokenExpiresAt,
|
||||||
|
arg.Nonce,
|
||||||
)
|
)
|
||||||
var i OidcToken
|
var i OidcToken
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
@@ -97,6 +104,7 @@ func (q *Queries) CreateOidcToken(ctx context.Context, arg CreateOidcTokenParams
|
|||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.TokenExpiresAt,
|
&i.TokenExpiresAt,
|
||||||
&i.RefreshTokenExpiresAt,
|
&i.RefreshTokenExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -148,7 +156,7 @@ func (q *Queries) CreateOidcUserInfo(ctx context.Context, arg CreateOidcUserInfo
|
|||||||
const deleteExpiredOidcCodes = `-- name: DeleteExpiredOidcCodes :many
|
const deleteExpiredOidcCodes = `-- name: DeleteExpiredOidcCodes :many
|
||||||
DELETE FROM "oidc_codes"
|
DELETE FROM "oidc_codes"
|
||||||
WHERE "expires_at" < ?
|
WHERE "expires_at" < ?
|
||||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) ([]OidcCode, error) {
|
func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) ([]OidcCode, error) {
|
||||||
@@ -167,6 +175,7 @@ func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) (
|
|||||||
&i.RedirectURI,
|
&i.RedirectURI,
|
||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.ExpiresAt,
|
&i.ExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -184,7 +193,7 @@ func (q *Queries) DeleteExpiredOidcCodes(ctx context.Context, expiresAt int64) (
|
|||||||
const deleteExpiredOidcTokens = `-- name: DeleteExpiredOidcTokens :many
|
const deleteExpiredOidcTokens = `-- name: DeleteExpiredOidcTokens :many
|
||||||
DELETE FROM "oidc_tokens"
|
DELETE FROM "oidc_tokens"
|
||||||
WHERE "token_expires_at" < ? AND "refresh_token_expires_at" < ?
|
WHERE "token_expires_at" < ? AND "refresh_token_expires_at" < ?
|
||||||
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at
|
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce
|
||||||
`
|
`
|
||||||
|
|
||||||
type DeleteExpiredOidcTokensParams struct {
|
type DeleteExpiredOidcTokensParams struct {
|
||||||
@@ -209,6 +218,7 @@ func (q *Queries) DeleteExpiredOidcTokens(ctx context.Context, arg DeleteExpired
|
|||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.TokenExpiresAt,
|
&i.TokenExpiresAt,
|
||||||
&i.RefreshTokenExpiresAt,
|
&i.RefreshTokenExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -276,7 +286,7 @@ func (q *Queries) DeleteOidcUserInfo(ctx context.Context, sub string) error {
|
|||||||
const getOidcCode = `-- name: GetOidcCode :one
|
const getOidcCode = `-- name: GetOidcCode :one
|
||||||
DELETE FROM "oidc_codes"
|
DELETE FROM "oidc_codes"
|
||||||
WHERE "code_hash" = ?
|
WHERE "code_hash" = ?
|
||||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, error) {
|
func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, error) {
|
||||||
@@ -289,6 +299,7 @@ func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, e
|
|||||||
&i.RedirectURI,
|
&i.RedirectURI,
|
||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.ExpiresAt,
|
&i.ExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -296,7 +307,7 @@ func (q *Queries) GetOidcCode(ctx context.Context, codeHash string) (OidcCode, e
|
|||||||
const getOidcCodeBySub = `-- name: GetOidcCodeBySub :one
|
const getOidcCodeBySub = `-- name: GetOidcCodeBySub :one
|
||||||
DELETE FROM "oidc_codes"
|
DELETE FROM "oidc_codes"
|
||||||
WHERE "sub" = ?
|
WHERE "sub" = ?
|
||||||
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at
|
RETURNING sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, error) {
|
func (q *Queries) GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, error) {
|
||||||
@@ -309,12 +320,13 @@ func (q *Queries) GetOidcCodeBySub(ctx context.Context, sub string) (OidcCode, e
|
|||||||
&i.RedirectURI,
|
&i.RedirectURI,
|
||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.ExpiresAt,
|
&i.ExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOidcCodeBySubUnsafe = `-- name: GetOidcCodeBySubUnsafe :one
|
const getOidcCodeBySubUnsafe = `-- name: GetOidcCodeBySubUnsafe :one
|
||||||
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at FROM "oidc_codes"
|
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce FROM "oidc_codes"
|
||||||
WHERE "sub" = ?
|
WHERE "sub" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -328,12 +340,13 @@ func (q *Queries) GetOidcCodeBySubUnsafe(ctx context.Context, sub string) (OidcC
|
|||||||
&i.RedirectURI,
|
&i.RedirectURI,
|
||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.ExpiresAt,
|
&i.ExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOidcCodeUnsafe = `-- name: GetOidcCodeUnsafe :one
|
const getOidcCodeUnsafe = `-- name: GetOidcCodeUnsafe :one
|
||||||
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at FROM "oidc_codes"
|
SELECT sub, code_hash, scope, redirect_uri, client_id, expires_at, nonce FROM "oidc_codes"
|
||||||
WHERE "code_hash" = ?
|
WHERE "code_hash" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -347,12 +360,13 @@ func (q *Queries) GetOidcCodeUnsafe(ctx context.Context, codeHash string) (OidcC
|
|||||||
&i.RedirectURI,
|
&i.RedirectURI,
|
||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.ExpiresAt,
|
&i.ExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOidcToken = `-- name: GetOidcToken :one
|
const getOidcToken = `-- name: GetOidcToken :one
|
||||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at FROM "oidc_tokens"
|
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce FROM "oidc_tokens"
|
||||||
WHERE "access_token_hash" = ?
|
WHERE "access_token_hash" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -367,12 +381,13 @@ func (q *Queries) GetOidcToken(ctx context.Context, accessTokenHash string) (Oid
|
|||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.TokenExpiresAt,
|
&i.TokenExpiresAt,
|
||||||
&i.RefreshTokenExpiresAt,
|
&i.RefreshTokenExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOidcTokenByRefreshToken = `-- name: GetOidcTokenByRefreshToken :one
|
const getOidcTokenByRefreshToken = `-- name: GetOidcTokenByRefreshToken :one
|
||||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at FROM "oidc_tokens"
|
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce FROM "oidc_tokens"
|
||||||
WHERE "refresh_token_hash" = ?
|
WHERE "refresh_token_hash" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -387,12 +402,13 @@ func (q *Queries) GetOidcTokenByRefreshToken(ctx context.Context, refreshTokenHa
|
|||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.TokenExpiresAt,
|
&i.TokenExpiresAt,
|
||||||
&i.RefreshTokenExpiresAt,
|
&i.RefreshTokenExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOidcTokenBySub = `-- name: GetOidcTokenBySub :one
|
const getOidcTokenBySub = `-- name: GetOidcTokenBySub :one
|
||||||
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at FROM "oidc_tokens"
|
SELECT sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce FROM "oidc_tokens"
|
||||||
WHERE "sub" = ?
|
WHERE "sub" = ?
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -407,6 +423,7 @@ func (q *Queries) GetOidcTokenBySub(ctx context.Context, sub string) (OidcToken,
|
|||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.TokenExpiresAt,
|
&i.TokenExpiresAt,
|
||||||
&i.RefreshTokenExpiresAt,
|
&i.RefreshTokenExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -437,7 +454,7 @@ UPDATE "oidc_tokens" SET
|
|||||||
"token_expires_at" = ?,
|
"token_expires_at" = ?,
|
||||||
"refresh_token_expires_at" = ?
|
"refresh_token_expires_at" = ?
|
||||||
WHERE "refresh_token_hash" = ?
|
WHERE "refresh_token_hash" = ?
|
||||||
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at
|
RETURNING sub, access_token_hash, refresh_token_hash, scope, client_id, token_expires_at, refresh_token_expires_at, nonce
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateOidcTokenByRefreshTokenParams struct {
|
type UpdateOidcTokenByRefreshTokenParams struct {
|
||||||
@@ -465,6 +482,7 @@ func (q *Queries) UpdateOidcTokenByRefreshToken(ctx context.Context, arg UpdateO
|
|||||||
&i.ClientID,
|
&i.ClientID,
|
||||||
&i.TokenExpiresAt,
|
&i.TokenExpiresAt,
|
||||||
&i.RefreshTokenExpiresAt,
|
&i.RefreshTokenExpiresAt,
|
||||||
|
&i.Nonce,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -50,13 +51,14 @@ type ClaimSet struct {
|
|||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
PreferredUsername string `json:"preferred_username,omitempty"`
|
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||||
Groups []string `json:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty"`
|
||||||
|
Nonce string `json:"nonce,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserinfoResponse struct {
|
type UserinfoResponse struct {
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name,omitempty"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email,omitempty"`
|
||||||
PreferredUsername string `json:"preferred_username"`
|
PreferredUsername string `json:"preferred_username,omitempty"`
|
||||||
Groups []string `json:"groups,omitempty"`
|
Groups []string `json:"groups,omitempty"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
@@ -76,6 +78,7 @@ type AuthorizeRequest struct {
|
|||||||
ClientID string `json:"client_id" binding:"required"`
|
ClientID string `json:"client_id" binding:"required"`
|
||||||
RedirectURI string `json:"redirect_uri" binding:"required"`
|
RedirectURI string `json:"redirect_uri" binding:"required"`
|
||||||
State string `json:"state" binding:"required"`
|
State string `json:"state" binding:"required"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OIDCServiceConfig struct {
|
type OIDCServiceConfig struct {
|
||||||
@@ -211,6 +214,9 @@ func (service *OIDCService) Init() error {
|
|||||||
|
|
||||||
for id, client := range service.config.Clients {
|
for id, client := range service.config.Clients {
|
||||||
client.ID = id
|
client.ID = id
|
||||||
|
if client.Name == "" {
|
||||||
|
client.Name = utils.Capitalize(client.ID)
|
||||||
|
}
|
||||||
service.clients[client.ClientID] = client
|
service.clients[client.ClientID] = client
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,6 +298,7 @@ func (service *OIDCService) StoreCode(c *gin.Context, sub string, code string, r
|
|||||||
RedirectURI: req.RedirectURI,
|
RedirectURI: req.RedirectURI,
|
||||||
ClientID: req.ClientID,
|
ClientID: req.ClientID,
|
||||||
ExpiresAt: expiresAt,
|
ExpiresAt: expiresAt,
|
||||||
|
Nonce: req.Nonce,
|
||||||
})
|
})
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -353,7 +360,7 @@ func (service *OIDCService) GetCodeEntry(c *gin.Context, codeHash string) (repos
|
|||||||
return oidcCode, nil
|
return oidcCode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *OIDCService) generateIDToken(client config.OIDCClientConfig, user repository.OidcUserinfo, scope string) (string, error) {
|
func (service *OIDCService) generateIDToken(client config.OIDCClientConfig, user repository.OidcUserinfo, scope string, nonce string) (string, error) {
|
||||||
createdAt := time.Now().Unix()
|
createdAt := time.Now().Unix()
|
||||||
expiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry) * time.Second).Unix()
|
expiresAt := time.Now().Add(time.Duration(service.config.SessionExpiry) * time.Second).Unix()
|
||||||
|
|
||||||
@@ -383,6 +390,7 @@ func (service *OIDCService) generateIDToken(client config.OIDCClientConfig, user
|
|||||||
Email: userInfo.Email,
|
Email: userInfo.Email,
|
||||||
PreferredUsername: userInfo.PreferredUsername,
|
PreferredUsername: userInfo.PreferredUsername,
|
||||||
Groups: userInfo.Groups,
|
Groups: userInfo.Groups,
|
||||||
|
Nonce: nonce,
|
||||||
}
|
}
|
||||||
|
|
||||||
payload, err := json.Marshal(claims)
|
payload, err := json.Marshal(claims)
|
||||||
@@ -406,14 +414,14 @@ func (service *OIDCService) generateIDToken(client config.OIDCClientConfig, user
|
|||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (service *OIDCService) GenerateAccessToken(c *gin.Context, client config.OIDCClientConfig, sub string, scope string) (TokenResponse, error) {
|
func (service *OIDCService) GenerateAccessToken(c *gin.Context, client config.OIDCClientConfig, codeEntry repository.OidcCode) (TokenResponse, error) {
|
||||||
user, err := service.GetUserinfo(c, sub)
|
user, err := service.GetUserinfo(c, codeEntry.Sub)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TokenResponse{}, err
|
return TokenResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
idToken, err := service.generateIDToken(client, user, scope)
|
idToken, err := service.generateIDToken(client, user, codeEntry.Sub, codeEntry.Nonce)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TokenResponse{}, err
|
return TokenResponse{}, err
|
||||||
@@ -433,15 +441,15 @@ func (service *OIDCService) GenerateAccessToken(c *gin.Context, client config.OI
|
|||||||
TokenType: "Bearer",
|
TokenType: "Bearer",
|
||||||
ExpiresIn: int64(service.config.SessionExpiry),
|
ExpiresIn: int64(service.config.SessionExpiry),
|
||||||
IDToken: idToken,
|
IDToken: idToken,
|
||||||
Scope: strings.ReplaceAll(scope, ",", " "),
|
Scope: strings.ReplaceAll(codeEntry.Scope, ",", " "),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = service.queries.CreateOidcToken(c, repository.CreateOidcTokenParams{
|
_, err = service.queries.CreateOidcToken(c, repository.CreateOidcTokenParams{
|
||||||
Sub: sub,
|
Sub: codeEntry.Sub,
|
||||||
AccessTokenHash: service.Hash(accessToken),
|
AccessTokenHash: service.Hash(accessToken),
|
||||||
RefreshTokenHash: service.Hash(refreshToken),
|
RefreshTokenHash: service.Hash(refreshToken),
|
||||||
ClientID: client.ClientID,
|
ClientID: client.ClientID,
|
||||||
Scope: scope,
|
Scope: codeEntry.Scope,
|
||||||
TokenExpiresAt: tokenExpiresAt,
|
TokenExpiresAt: tokenExpiresAt,
|
||||||
RefreshTokenExpiresAt: refrshTokenExpiresAt,
|
RefreshTokenExpiresAt: refrshTokenExpiresAt,
|
||||||
})
|
})
|
||||||
@@ -480,7 +488,7 @@ func (service *OIDCService) RefreshAccessToken(c *gin.Context, refreshToken stri
|
|||||||
|
|
||||||
idToken, err := service.generateIDToken(config.OIDCClientConfig{
|
idToken, err := service.generateIDToken(config.OIDCClientConfig{
|
||||||
ClientID: entry.ClientID,
|
ClientID: entry.ClientID,
|
||||||
}, user, entry.Scope)
|
}, user, entry.Scope, entry.Nonce)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TokenResponse{}, err
|
return TokenResponse{}, err
|
||||||
@@ -665,10 +673,21 @@ func (service *OIDCService) Cleanup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (service *OIDCService) GetJWK() ([]byte, error) {
|
func (service *OIDCService) GetJWK() ([]byte, error) {
|
||||||
|
hasher := sha256.New()
|
||||||
|
|
||||||
|
der := x509.MarshalPKCS1PublicKey(&service.privateKey.PublicKey)
|
||||||
|
|
||||||
|
if der == nil {
|
||||||
|
return nil, errors.New("failed to marshal public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
hasher.Write(der)
|
||||||
|
|
||||||
jwk := jose.JSONWebKey{
|
jwk := jose.JSONWebKey{
|
||||||
Key: service.privateKey,
|
Key: service.privateKey,
|
||||||
Algorithm: string(jose.RS256),
|
Algorithm: string(jose.RS256),
|
||||||
Use: "sig",
|
Use: "sig",
|
||||||
|
KeyID: base64.URLEncoding.EncodeToString(hasher.Sum(nil)),
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwk.Public().MarshalJSON()
|
return jwk.Public().MarshalJSON()
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package loaders
|
package loaders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/traefik/paerser/cli"
|
"github.com/traefik/paerser/cli"
|
||||||
"github.com/traefik/paerser/file"
|
"github.com/traefik/paerser/file"
|
||||||
@@ -16,11 +18,16 @@ func (f *FileLoader) Load(args []string, cmd *cli.Command) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// I guess we are using traefik as the root name
|
// I guess we are using traefik as the root name (we can't change it)
|
||||||
configFileFlag := "traefik.experimental.configFile"
|
configFileFlag := "traefik.experimental.configfile"
|
||||||
|
envVar := "TINYAUTH_EXPERIMENTAL_CONFIGFILE"
|
||||||
|
|
||||||
if _, ok := flags[configFileFlag]; !ok {
|
if _, ok := flags[configFileFlag]; !ok {
|
||||||
return false, nil
|
if value := os.Getenv(envVar); value != "" {
|
||||||
|
flags[configFileFlag] = value
|
||||||
|
} else {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn().Msg("Using experimental file config loader, this feature is experimental and may change or be removed in future releases")
|
log.Warn().Msg("Using experimental file config loader, this feature is experimental and may change or be removed in future releases")
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ INSERT INTO "oidc_codes" (
|
|||||||
"scope",
|
"scope",
|
||||||
"redirect_uri",
|
"redirect_uri",
|
||||||
"client_id",
|
"client_id",
|
||||||
"expires_at"
|
"expires_at",
|
||||||
|
"nonce"
|
||||||
) VALUES (
|
) VALUES (
|
||||||
?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?
|
||||||
)
|
)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
@@ -45,9 +46,10 @@ INSERT INTO "oidc_tokens" (
|
|||||||
"scope",
|
"scope",
|
||||||
"client_id",
|
"client_id",
|
||||||
"token_expires_at",
|
"token_expires_at",
|
||||||
"refresh_token_expires_at"
|
"refresh_token_expires_at",
|
||||||
|
"nonce"
|
||||||
) VALUES (
|
) VALUES (
|
||||||
?, ?, ?, ?, ?, ?, ?
|
?, ?, ?, ?, ?, ?, ?, ?
|
||||||
)
|
)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ CREATE TABLE IF NOT EXISTS "oidc_codes" (
|
|||||||
"scope" TEXT NOT NULL,
|
"scope" TEXT NOT NULL,
|
||||||
"redirect_uri" TEXT NOT NULL,
|
"redirect_uri" TEXT NOT NULL,
|
||||||
"client_id" TEXT NOT NULL,
|
"client_id" TEXT NOT NULL,
|
||||||
"expires_at" INTEGER NOT NULL
|
"expires_at" INTEGER NOT NULL,
|
||||||
|
"nonce" TEXT DEFAULT ""
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "oidc_tokens" (
|
CREATE TABLE IF NOT EXISTS "oidc_tokens" (
|
||||||
@@ -14,7 +15,8 @@ CREATE TABLE IF NOT EXISTS "oidc_tokens" (
|
|||||||
"scope" TEXT NOT NULL,
|
"scope" TEXT NOT NULL,
|
||||||
"client_id" TEXT NOT NULL,
|
"client_id" TEXT NOT NULL,
|
||||||
"token_expires_at" INTEGER NOT NULL,
|
"token_expires_at" INTEGER NOT NULL,
|
||||||
"refresh_token_expires_at" INTEGER NOT NULL
|
"refresh_token_expires_at" INTEGER NOT NULL,
|
||||||
|
"nonce" TEXT DEFAULT ""
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "oidc_userinfo" (
|
CREATE TABLE IF NOT EXISTS "oidc_userinfo" (
|
||||||
|
|||||||
Reference in New Issue
Block a user