mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-29 05:05:42 +00:00
Merge branch 'main' into feat/new-ui
This commit is contained in:
@@ -29,4 +29,4 @@ LOG_LEVEL=0
|
|||||||
APP_TITLE=Tinyauth SSO
|
APP_TITLE=Tinyauth SSO
|
||||||
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
|
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
|
||||||
OAUTH_AUTO_REDIRECT=none
|
OAUTH_AUTO_REDIRECT=none
|
||||||
BACKGROUND_IMAGE=some_image_url
|
BACKGROUND_IMAGE=some_image_url
|
||||||
|
|||||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -2,12 +2,22 @@ version: 2
|
|||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "bun"
|
- package-ecosystem: "bun"
|
||||||
directory: "/frontend"
|
directory: "/frontend"
|
||||||
|
minor-patch:
|
||||||
|
update-types:
|
||||||
|
- "patch"
|
||||||
|
- "minor"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
|
||||||
- package-ecosystem: "gomod"
|
- package-ecosystem: "gomod"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
minor-patch:
|
||||||
|
update-types:
|
||||||
|
- "patch"
|
||||||
|
- "minor"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
|
||||||
- package-ecosystem: "docker"
|
- package-ecosystem: "docker"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
31
.github/workflows/sponsors.yml
vendored
Normal file
31
.github/workflows/sponsors.yml
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: Generate Sponsors List
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-sponsors:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate Sponsors
|
||||||
|
uses: JamesIves/github-sponsors-readme-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.SPONSORS_GENERATOR_PAT }}
|
||||||
|
active-only: false
|
||||||
|
file: "README.md"
|
||||||
|
template: '<a href="https://github.com/{{{ login }}}"><img src="{{{ avatarUrl }}}" width="64px" alt="User avatar: {{{ login }}}" /></a> '
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
commit-message: |
|
||||||
|
docs: regenerate readme sponsors list
|
||||||
|
committer: GitHub <noreply@github.com>
|
||||||
|
author: GitHub <noreply@github.com>
|
||||||
|
branch: docs/update-readme
|
||||||
|
title: |
|
||||||
|
docs: regenerate readme sponsors list
|
||||||
|
labels: bot
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Site builder
|
# Site builder
|
||||||
FROM oven/bun:1.2.10-alpine AS frontend-builder
|
FROM oven/bun:1.2.12-alpine AS frontend-builder
|
||||||
|
|
||||||
WORKDIR /frontend
|
WORKDIR /frontend
|
||||||
|
|
||||||
|
|||||||
12
cmd/root.go
12
cmd/root.go
@@ -92,6 +92,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
ForgotPasswordMessage: config.FogotPasswordMessage,
|
ForgotPasswordMessage: config.FogotPasswordMessage,
|
||||||
BackgroundImage: config.BackgroundImage,
|
BackgroundImage: config.BackgroundImage,
|
||||||
|
OAuthAutoRedirect: config.OAuthAutoRedirect,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create api config
|
// Create api config
|
||||||
@@ -112,6 +113,11 @@ var rootCmd = &cobra.Command{
|
|||||||
LoginMaxRetries: config.LoginMaxRetries,
|
LoginMaxRetries: config.LoginMaxRetries,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create hooks config
|
||||||
|
hooksConfig := types.HooksConfig{
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
|
||||||
// Create docker service
|
// Create docker service
|
||||||
docker := docker.NewDocker()
|
docker := docker.NewDocker()
|
||||||
|
|
||||||
@@ -129,7 +135,7 @@ var rootCmd = &cobra.Command{
|
|||||||
providers.Init()
|
providers.Init()
|
||||||
|
|
||||||
// Create hooks service
|
// Create hooks service
|
||||||
hooks := hooks.NewHooks(auth, providers)
|
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
||||||
|
|
||||||
// Create handlers
|
// Create handlers
|
||||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||||
@@ -190,9 +196,10 @@ func init() {
|
|||||||
rootCmd.Flags().String("generic-auth-url", "", "Generic OAuth auth URL.")
|
rootCmd.Flags().String("generic-auth-url", "", "Generic OAuth auth URL.")
|
||||||
rootCmd.Flags().String("generic-token-url", "", "Generic OAuth token URL.")
|
rootCmd.Flags().String("generic-token-url", "", "Generic OAuth token URL.")
|
||||||
rootCmd.Flags().String("generic-user-url", "", "Generic OAuth user info URL.")
|
rootCmd.Flags().String("generic-user-url", "", "Generic OAuth user info URL.")
|
||||||
rootCmd.Flags().String("generic-name", "Other", "Generic OAuth provider name.")
|
rootCmd.Flags().String("generic-name", "Generic", "Generic OAuth provider name.")
|
||||||
rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.")
|
rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.")
|
||||||
rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.")
|
rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.")
|
||||||
|
rootCmd.Flags().String("oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)")
|
||||||
rootCmd.Flags().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.")
|
rootCmd.Flags().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.")
|
||||||
rootCmd.Flags().Int("login-timeout", 300, "Login timeout in seconds after max retries reached (0 to disable).")
|
rootCmd.Flags().Int("login-timeout", 300, "Login timeout in seconds after max retries reached (0 to disable).")
|
||||||
rootCmd.Flags().Int("login-max-retries", 5, "Maximum login attempts before timeout (0 to disable).")
|
rootCmd.Flags().Int("login-max-retries", 5, "Maximum login attempts before timeout (0 to disable).")
|
||||||
@@ -226,6 +233,7 @@ func init() {
|
|||||||
viper.BindEnv("generic-name", "GENERIC_NAME")
|
viper.BindEnv("generic-name", "GENERIC_NAME")
|
||||||
viper.BindEnv("disable-continue", "DISABLE_CONTINUE")
|
viper.BindEnv("disable-continue", "DISABLE_CONTINUE")
|
||||||
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
|
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
|
||||||
|
viper.BindEnv("oauth-auto-redirect", "OAUTH_AUTO_REDIRECT")
|
||||||
viper.BindEnv("session-expiry", "SESSION_EXPIRY")
|
viper.BindEnv("session-expiry", "SESSION_EXPIRY")
|
||||||
viper.BindEnv("log-level", "LOG_LEVEL")
|
viper.BindEnv("log-level", "LOG_LEVEL")
|
||||||
viper.BindEnv("app-title", "APP_TITLE")
|
viper.BindEnv("app-title", "APP_TITLE")
|
||||||
|
|||||||
@@ -55,4 +55,4 @@
|
|||||||
"typescript-eslint": "^8.26.1",
|
"typescript-eslint": "^8.26.1",
|
||||||
"vite": "^6.3.1"
|
"vite": "^6.3.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "أدخل رمز TOTP الخاص بك",
|
"totpTitle": "أدخل رمز TOTP الخاص بك",
|
||||||
"unauthorizedTitle": "غير مرخص",
|
"unauthorizedTitle": "غير مرخص",
|
||||||
"unauthorizedResourceSubtitle": "المستخدم الذي يحمل اسم المستخدم <Code>{{username}}</Code> غير مصرح له بالوصول إلى المورد <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "المستخدم الذي يحمل اسم المستخدم <Code>{{username}}</Code> غير مصرح له بالوصول إلى المورد <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "المستخدم الذي يحمل اسم المستخدم <Code>{{username}}</Code> غير مصرح له بتسجيل الدخول.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "حاول مجددا",
|
"unauthorizedButton": "حاول مجددا",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "إلغاء"
|
"cancelTitle": "إلغاء",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Geben Sie Ihren TOTP Code ein",
|
"totpTitle": "Geben Sie Ihren TOTP Code ein",
|
||||||
"unauthorizedTitle": "Unautorisiert",
|
"unauthorizedTitle": "Unautorisiert",
|
||||||
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <Code>{{username}}</Code> ist nicht berechtigt auf die Ressource <Code>{{resource}}</Code> zuzugreifen.",
|
"unauthorizedResourceSubtitle": "Der Benutzer mit Benutzername <Code>{{username}}</Code> ist nicht berechtigt auf die Ressource <Code>{{resource}}</Code> zuzugreifen.",
|
||||||
"unaothorizedLoginSubtitle": "Der Benutzer mit dem Benutzernamen <Code>{{username}}</Code> ist nicht berechtigt, sich einzuloggen.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Erneut versuchen",
|
"unauthorizedButton": "Erneut versuchen",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Nicht vertrauenswürdige Weiterleitung",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "Sie versuchen auf eine Domain umzuleiten, die nicht mit Ihrer konfigurierten Domain übereinstimmt (<Code>{{domain}}</Code>). Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Abbrechen",
|
||||||
|
"forgotPasswordTitle": "Passwort vergessen?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Εισάγετε τον κωδικό TOTP",
|
"totpTitle": "Εισάγετε τον κωδικό TOTP",
|
||||||
"unauthorizedTitle": "Μη εξουσιοδοτημένο",
|
"unauthorizedTitle": "Μη εξουσιοδοτημένο",
|
||||||
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν έχει άδεια πρόσβασης στον πόρο <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν έχει άδεια πρόσβασης στον πόρο <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
|
"unauthorizedLoginSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι εξουσιοδοτημένος να συνδεθεί.",
|
||||||
|
"unauthorizedGroupsSubtitle": "Ο χρήστης με όνομα χρήστη <Code>{{username}}</Code> δεν είναι στις ομάδες που απαιτούνται από τον πόρο <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Προσπαθήστε ξανά",
|
"unauthorizedButton": "Προσπαθήστε ξανά",
|
||||||
"untrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
|
"untrustedRedirectTitle": "Μη έμπιστη ανακατεύθυνση",
|
||||||
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε έναν τομέα που δεν ταιριάζει με τον ρυθμισμένο τομέα σας (<Code>{{domain}}</Code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
"untrustedRedirectSubtitle": "Προσπαθείτε να ανακατευθύνετε σε έναν τομέα που δεν ταιριάζει με τον ρυθμισμένο τομέα σας (<Code>{{domain}}</Code>). Είστε βέβαιοι ότι θέλετε να συνεχίσετε;",
|
||||||
"cancelTitle": "Ακύρωση"
|
"cancelTitle": "Ακύρωση",
|
||||||
|
"forgotPasswordTitle": "Ξεχάσατε το συνθηματικό σας;"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Saisissez votre code TOTP",
|
"totpTitle": "Saisissez votre code TOTP",
|
||||||
"unauthorizedTitle": "Non autorisé",
|
"unauthorizedTitle": "Non autorisé",
|
||||||
"unauthorizedResourceSubtitle": "L'utilisateur avec le nom d'utilisateur <Code>{{username}}</Code> n'est pas autorisé à accéder à la ressource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "L'utilisateur avec le nom d'utilisateur <Code>{{username}}</Code> n'est pas autorisé à accéder à la ressource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "L'utilisateur avec le nom d'utilisateur <Code>{{username}}</Code> n'est pas autorisé à se connecter.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Réessayer",
|
"unauthorizedButton": "Réessayer",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Voer je TOTP-code in",
|
"totpTitle": "Voer je TOTP-code in",
|
||||||
"unauthorizedTitle": "Ongeautoriseerd",
|
"unauthorizedTitle": "Ongeautoriseerd",
|
||||||
"unauthorizedResourceSubtitle": "De gebruiker met gebruikersnaam <Code>{{username}}</Code> heeft geen toegang tot de bron <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "De gebruiker met gebruikersnaam <Code>{{username}}</Code> heeft geen toegang tot de bron <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "De gebruiker met gebruikersnaam <Code>{{username}}</Code> is niet gemachtigd om in te loggen.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Opnieuw proberen",
|
"unauthorizedButton": "Opnieuw proberen",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Wprowadź kod TOTP",
|
"totpTitle": "Wprowadź kod TOTP",
|
||||||
"unauthorizedTitle": "Nieautoryzowany",
|
"unauthorizedTitle": "Nieautoryzowany",
|
||||||
"unauthorizedResourceSubtitle": "Użytkownik o nazwie <Code>{{username}}</Code> nie jest upoważniony do uzyskania dostępu do zasobu <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "Użytkownik o nazwie <Code>{{username}}</Code> nie jest upoważniony do uzyskania dostępu do zasobu <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "Użytkownik o nazwie <Code>{{username}}</Code> nie jest upoważniony do logowania.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Spróbuj ponownie",
|
"unauthorizedButton": "Spróbuj ponownie",
|
||||||
"untrustedRedirectTitle": "Niezaufane przekierowanie",
|
"untrustedRedirectTitle": "Niezaufane przekierowanie",
|
||||||
"untrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do skonfigurowanej przez Ciebie domeny (<Code>{{domain}}</Code>). Czy na pewno chcesz kontynuować?",
|
"untrustedRedirectSubtitle": "Próbujesz przekierować do domeny, która nie pasuje do skonfigurowanej przez Ciebie domeny (<Code>{{domain}}</Code>). Czy na pewno chcesz kontynuować?",
|
||||||
"cancelTitle": "Anuluj"
|
"cancelTitle": "Anuluj",
|
||||||
|
"forgotPasswordTitle": "Nie pamiętasz hasła?"
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"loginTitle": "Bem-vindo de volta, faça o login com",
|
"loginTitle": "Bem-vindo de volta, acesse com",
|
||||||
"loginDivider": "Ou continuar com uma senha",
|
"loginDivider": "Ou continuar com uma senha",
|
||||||
"loginUsername": "Nome de usuário",
|
"loginUsername": "Nome de usuário",
|
||||||
"loginPassword": "Senha",
|
"loginPassword": "Senha",
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Insira o seu código TOTP",
|
"totpTitle": "Insira o seu código TOTP",
|
||||||
"unauthorizedTitle": "Não autorizado",
|
"unauthorizedTitle": "Não autorizado",
|
||||||
"unauthorizedResourceSubtitle": "O usuário com nome de usuário <Code>{{username}}</Code> não está autorizado a acessar o recurso <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "O usuário com nome de usuário <Code>{{username}}</Code> não está autorizado a acessar o recurso <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "O usuário com o nome <Code>{{username}}</Code> não está autorizado a acessar.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Tentar novamente",
|
"unauthorizedButton": "Tentar novamente",
|
||||||
"untrustedRedirectTitle": "Redirecionamento não confiável",
|
"untrustedRedirectTitle": "Redirecionamento não confiável",
|
||||||
"untrustedRedirectSubtitle": "Você está tentando redirecionar para um domínio que não corresponde ao seu domínio configurado (<Code>{{domain}}</Code>). Tem certeza que deseja continuar?",
|
"untrustedRedirectSubtitle": "Você está tentando redirecionar para um domínio que não corresponde ao seu domínio configurado (<Code>{{domain}}</Code>). Tem certeza que deseja continuar?",
|
||||||
"cancelTitle": "Cancelar"
|
"cancelTitle": "Cancelar",
|
||||||
|
"forgotPasswordTitle": "Esqueceu sua senha?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "İptal"
|
"cancelTitle": "İptal",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "输入您的 TOTP 代码",
|
"totpTitle": "输入您的 TOTP 代码",
|
||||||
"unauthorizedTitle": "未授权",
|
"unauthorizedTitle": "未授权",
|
||||||
"unauthorizedResourceSubtitle": "用户 <Code>{{username}}</Code> 无权访问资源 <Code>{{resource}}</Code>。",
|
"unauthorizedResourceSubtitle": "用户 <Code>{{username}}</Code> 无权访问资源 <Code>{{resource}}</Code>。",
|
||||||
"unaothorizedLoginSubtitle": "用户名 <Code>{{username}}</Code> 无登录权限。",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "重试",
|
"unauthorizedButton": "重试",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,11 @@
|
|||||||
"totpTitle": "Enter your TOTP code",
|
"totpTitle": "Enter your TOTP code",
|
||||||
"unauthorizedTitle": "Unauthorized",
|
"unauthorizedTitle": "Unauthorized",
|
||||||
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
"unauthorizedResourceSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to access the resource <Code>{{resource}}</Code>.",
|
||||||
"unaothorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
"unauthorizedLoginSubtitle": "The user with username <Code>{{username}}</Code> is not authorized to login.",
|
||||||
|
"unauthorizedGroupsSubtitle": "The user with username <Code>{{username}}</Code> is not in the groups required by the resource <Code>{{resource}}</Code>.",
|
||||||
"unauthorizedButton": "Try again",
|
"unauthorizedButton": "Try again",
|
||||||
"untrustedRedirectTitle": "Untrusted redirect",
|
"untrustedRedirectTitle": "Untrusted redirect",
|
||||||
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
"untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<Code>{{domain}}</Code>). Are you sure you want to continue?",
|
||||||
"cancelTitle": "Cancel"
|
"cancelTitle": "Cancel",
|
||||||
|
"forgotPasswordTitle": "Forgot your password?"
|
||||||
}
|
}
|
||||||
@@ -94,6 +94,14 @@ export const LoginPage = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isMounted()) {
|
||||||
|
if (oauthConfigured && configuredProviders.includes(oauthAutoRedirect)) {
|
||||||
|
oauthMutation.mutate(oauthAutoRedirect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="min-w-xs sm:min-w-sm">
|
<Card className="min-w-xs sm:min-w-sm">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const appContextSchema = z.object({
|
|||||||
genericName: z.string(),
|
genericName: z.string(),
|
||||||
domain: z.string(),
|
domain: z.string(),
|
||||||
forgotPasswordMessage: z.string(),
|
forgotPasswordMessage: z.string(),
|
||||||
oauthAutoRedirect: z.string(),
|
oauthAutoRedirect: z.enum(["none", "github", "google", "generic"]),
|
||||||
backgroundImage: z.string(),
|
backgroundImage: z.string(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
12
go.mod
12
go.mod
@@ -10,7 +10,7 @@ require (
|
|||||||
github.com/rs/zerolog v1.34.0
|
github.com/rs/zerolog v1.34.0
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
golang.org/x/crypto v0.37.0
|
golang.org/x/crypto v0.38.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -25,7 +25,7 @@ require (
|
|||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.34.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||||
golang.org/x/term v0.31.0 // indirect
|
golang.org/x/term v0.32.0 // indirect
|
||||||
gotest.tools/v3 v3.5.2 // indirect
|
gotest.tools/v3 v3.5.2 // indirect
|
||||||
rsc.io/qr v0.2.0 // indirect
|
rsc.io/qr v0.2.0 // indirect
|
||||||
)
|
)
|
||||||
@@ -103,10 +103,10 @@ require (
|
|||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/arch v0.13.0 // indirect
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
golang.org/x/net v0.38.0 // indirect
|
golang.org/x/net v0.38.0 // indirect
|
||||||
golang.org/x/oauth2 v0.29.0
|
golang.org/x/oauth2 v0.30.0
|
||||||
golang.org/x/sync v0.13.0 // indirect
|
golang.org/x/sync v0.14.0 // indirect
|
||||||
golang.org/x/sys v0.32.0 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.25.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.3 // indirect
|
google.golang.org/protobuf v1.36.3 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
24
go.sum
24
go.sum
@@ -269,8 +269,8 @@ golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
@@ -281,13 +281,13 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
|
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
|
||||||
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -297,14 +297,14 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||||
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ var authConfig = types.AuthConfig{
|
|||||||
LoginMaxRetries: 0,
|
LoginMaxRetries: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple hooks config for tests
|
||||||
|
var hooksConfig = types.HooksConfig{
|
||||||
|
Domain: "localhost",
|
||||||
|
}
|
||||||
|
|
||||||
// Cookie
|
// Cookie
|
||||||
var cookie string
|
var cookie string
|
||||||
|
|
||||||
@@ -83,7 +88,7 @@ func getAPI(t *testing.T) *api.API {
|
|||||||
providers.Init()
|
providers.Init()
|
||||||
|
|
||||||
// Create hooks service
|
// Create hooks service
|
||||||
hooks := hooks.NewHooks(auth, providers)
|
hooks := hooks.NewHooks(hooksConfig, auth, providers)
|
||||||
|
|
||||||
// Create handlers service
|
// Create handlers service
|
||||||
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
handlers := handlers.NewHandlers(handlersConfig, auth, hooks, providers, docker)
|
||||||
|
|||||||
@@ -160,9 +160,12 @@ func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie)
|
|||||||
|
|
||||||
// Set data
|
// Set data
|
||||||
session.Values["username"] = data.Username
|
session.Values["username"] = data.Username
|
||||||
|
session.Values["name"] = data.Name
|
||||||
|
session.Values["email"] = data.Email
|
||||||
session.Values["provider"] = data.Provider
|
session.Values["provider"] = data.Provider
|
||||||
session.Values["expiry"] = time.Now().Add(time.Duration(sessionExpiry) * time.Second).Unix()
|
session.Values["expiry"] = time.Now().Add(time.Duration(sessionExpiry) * time.Second).Unix()
|
||||||
session.Values["totpPending"] = data.TotpPending
|
session.Values["totpPending"] = data.TotpPending
|
||||||
|
session.Values["oauthGroups"] = data.OAuthGroups
|
||||||
|
|
||||||
// Save session
|
// Save session
|
||||||
err = session.Save(c.Request, c.Writer)
|
err = session.Save(c.Request, c.Writer)
|
||||||
@@ -211,14 +214,24 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) (types.SessionCookie, error)
|
|||||||
return types.SessionCookie{}, err
|
return types.SessionCookie{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("Got session")
|
||||||
|
|
||||||
// Get data from session
|
// Get data from session
|
||||||
username, usernameOk := session.Values["username"].(string)
|
username, usernameOk := session.Values["username"].(string)
|
||||||
|
email, emailOk := session.Values["email"].(string)
|
||||||
|
name, nameOk := session.Values["name"].(string)
|
||||||
provider, providerOK := session.Values["provider"].(string)
|
provider, providerOK := session.Values["provider"].(string)
|
||||||
expiry, expiryOk := session.Values["expiry"].(int64)
|
expiry, expiryOk := session.Values["expiry"].(int64)
|
||||||
totpPending, totpPendingOk := session.Values["totpPending"].(bool)
|
totpPending, totpPendingOk := session.Values["totpPending"].(bool)
|
||||||
|
oauthGroups, oauthGroupsOk := session.Values["oauthGroups"].(string)
|
||||||
|
|
||||||
if !usernameOk || !providerOK || !expiryOk || !totpPendingOk {
|
if !usernameOk || !providerOK || !expiryOk || !totpPendingOk || !emailOk || !nameOk || !oauthGroupsOk {
|
||||||
log.Warn().Msg("Session cookie is missing data")
|
log.Warn().Msg("Session cookie is invalid")
|
||||||
|
|
||||||
|
// If any data is missing, delete the session cookie
|
||||||
|
auth.DeleteSessionCookie(c)
|
||||||
|
|
||||||
|
// Return empty cookie
|
||||||
return types.SessionCookie{}, nil
|
return types.SessionCookie{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,13 +246,16 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) (types.SessionCookie, error)
|
|||||||
return types.SessionCookie{}, nil
|
return types.SessionCookie{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Str("username", username).Str("provider", provider).Int64("expiry", expiry).Bool("totpPending", totpPending).Msg("Parsed cookie")
|
log.Debug().Str("username", username).Str("provider", provider).Int64("expiry", expiry).Bool("totpPending", totpPending).Str("name", name).Str("email", email).Str("oauthGroups", oauthGroups).Msg("Parsed cookie")
|
||||||
|
|
||||||
// Return the cookie
|
// Return the cookie
|
||||||
return types.SessionCookie{
|
return types.SessionCookie{
|
||||||
Username: username,
|
Username: username,
|
||||||
|
Name: name,
|
||||||
|
Email: email,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
TotpPending: totpPending,
|
TotpPending: totpPending,
|
||||||
|
OAuthGroups: oauthGroups,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,48 +264,52 @@ func (auth *Auth) UserAuthConfigured() bool {
|
|||||||
return len(auth.Config.Users) > 0
|
return len(auth.Config.Users) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bool, error) {
|
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
||||||
// Get headers
|
|
||||||
host := c.Request.Header.Get("X-Forwarded-Host")
|
|
||||||
|
|
||||||
// Get app id
|
|
||||||
appId := strings.Split(host, ".")[0]
|
|
||||||
|
|
||||||
// Get the container labels
|
|
||||||
labels, err := auth.Docker.GetLabels(appId)
|
|
||||||
|
|
||||||
// If there is an error, return false
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if oauth is allowed
|
// Check if oauth is allowed
|
||||||
if context.OAuth {
|
if context.OAuth {
|
||||||
log.Debug().Msg("Checking OAuth whitelist")
|
log.Debug().Msg("Checking OAuth whitelist")
|
||||||
return utils.CheckWhitelist(labels.OAuthWhitelist, context.Username), nil
|
return utils.CheckWhitelist(labels.OAuthWhitelist, context.Email)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check users
|
// Check users
|
||||||
log.Debug().Msg("Checking users")
|
log.Debug().Msg("Checking users")
|
||||||
|
|
||||||
return utils.CheckWhitelist(labels.Users, context.Username), nil
|
return utils.CheckWhitelist(labels.Users, context.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) AuthEnabled(c *gin.Context) (bool, error) {
|
func (auth *Auth) OAuthGroup(c *gin.Context, context types.UserContext, labels types.TinyauthLabels) bool {
|
||||||
|
// Check if groups are required
|
||||||
|
if labels.OAuthGroups == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we are using the generic oauth provider
|
||||||
|
if context.Provider != "generic" {
|
||||||
|
log.Debug().Msg("Not using generic provider, skipping group check")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the groups by comma (no need to parse since they are from the API response)
|
||||||
|
oauthGroups := strings.Split(context.OAuthGroups, ",")
|
||||||
|
|
||||||
|
// For every group check if it is in the required groups
|
||||||
|
for _, group := range oauthGroups {
|
||||||
|
if utils.CheckWhitelist(labels.OAuthGroups, group) {
|
||||||
|
log.Debug().Str("group", group).Msg("Group is in required groups")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No groups matched
|
||||||
|
log.Debug().Msg("No groups matched")
|
||||||
|
|
||||||
|
// Return false
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) AuthEnabled(c *gin.Context, labels types.TinyauthLabels) (bool, error) {
|
||||||
// Get headers
|
// Get headers
|
||||||
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
uri := c.Request.Header.Get("X-Forwarded-Uri")
|
||||||
host := c.Request.Header.Get("X-Forwarded-Host")
|
|
||||||
|
|
||||||
// Get app id
|
|
||||||
appId := strings.Split(host, ".")[0]
|
|
||||||
|
|
||||||
// Get the container labels
|
|
||||||
labels, err := auth.Docker.GetLabels(appId)
|
|
||||||
|
|
||||||
// If there is an error, auth enabled
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the allowed label is empty
|
// Check if the allowed label is empty
|
||||||
if labels.Allowed == "" {
|
if labels.Allowed == "" {
|
||||||
|
|||||||
@@ -6,4 +6,13 @@ var TinyauthLabels = []string{
|
|||||||
"tinyauth.users",
|
"tinyauth.users",
|
||||||
"tinyauth.allowed",
|
"tinyauth.allowed",
|
||||||
"tinyauth.headers",
|
"tinyauth.headers",
|
||||||
|
"tinyauth.oauth.groups",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claims are the OIDC supported claims (including preferd username for some reason)
|
||||||
|
type Claims struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
PreferredUsername string `json:"preferred_username"`
|
||||||
|
Groups []string `json:"groups"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import (
|
|||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
"tinyauth/internal/utils"
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
apiTypes "github.com/docker/docker/api/types"
|
container "github.com/docker/docker/api/types/container"
|
||||||
containerTypes "github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@@ -30,17 +29,22 @@ func (docker *Docker) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the context and api client
|
// Create the context
|
||||||
docker.Context = context.Background()
|
docker.Context = context.Background()
|
||||||
|
|
||||||
|
// Negotiate API version
|
||||||
|
client.NegotiateAPIVersion(docker.Context)
|
||||||
|
|
||||||
|
// Set client
|
||||||
docker.Client = client
|
docker.Client = client
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) GetContainers() ([]apiTypes.Container, error) {
|
func (docker *Docker) GetContainers() ([]container.Summary, error) {
|
||||||
// Get the list of containers
|
// Get the list of containers
|
||||||
containers, err := docker.Client.ContainerList(docker.Context, containerTypes.ListOptions{})
|
containers, err := docker.Client.ContainerList(docker.Context, container.ListOptions{})
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -51,13 +55,13 @@ func (docker *Docker) GetContainers() ([]apiTypes.Container, error) {
|
|||||||
return containers, nil
|
return containers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (docker *Docker) InspectContainer(containerId string) (apiTypes.ContainerJSON, error) {
|
func (docker *Docker) InspectContainer(containerId string) (container.InspectResponse, error) {
|
||||||
// Inspect the container
|
// Inspect the container
|
||||||
inspect, err := docker.Client.ContainerInspect(docker.Context, containerId)
|
inspect, err := docker.Client.ContainerInspect(docker.Context, containerId)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apiTypes.ContainerJSON{}, err
|
return container.InspectResponse{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the inspect
|
// Return the inspect
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"tinyauth/internal/hooks"
|
"tinyauth/internal/hooks"
|
||||||
"tinyauth/internal/providers"
|
"tinyauth/internal/providers"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/go-querystring/query"
|
"github.com/google/go-querystring/query"
|
||||||
@@ -68,12 +69,17 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
proto := c.Request.Header.Get("X-Forwarded-Proto")
|
||||||
host := c.Request.Header.Get("X-Forwarded-Host")
|
host := c.Request.Header.Get("X-Forwarded-Host")
|
||||||
|
|
||||||
// Check if auth is enabled
|
// Get the app id
|
||||||
authEnabled, err := h.Auth.AuthEnabled(c)
|
appId := strings.Split(host, ".")[0]
|
||||||
|
|
||||||
|
// Get the container labels
|
||||||
|
labels, err := h.Docker.GetLabels(appId)
|
||||||
|
|
||||||
|
log.Debug().Interface("labels", labels).Msg("Got labels")
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to check if app is allowed")
|
log.Error().Err(err).Msg("Failed to get container labels")
|
||||||
|
|
||||||
if proxy.Proxy == "nginx" || !isBrowser {
|
if proxy.Proxy == "nginx" || !isBrowser {
|
||||||
c.JSON(500, gin.H{
|
c.JSON(500, gin.H{
|
||||||
@@ -87,11 +93,8 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the app id
|
// Check if auth is enabled
|
||||||
appId := strings.Split(host, ".")[0]
|
authEnabled, err := h.Auth.AuthEnabled(c, labels)
|
||||||
|
|
||||||
// Get the container labels
|
|
||||||
labels, err := h.Docker.GetLabels(appId)
|
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -113,7 +116,7 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
if !authEnabled {
|
if !authEnabled {
|
||||||
for key, value := range labels.Headers {
|
for key, value := range labels.Headers {
|
||||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||||
c.Header(key, value)
|
c.Header(key, utils.SanitizeHeader(value))
|
||||||
}
|
}
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
@@ -125,28 +128,18 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
// Get user context
|
// Get user context
|
||||||
userContext := h.Hooks.UseUserContext(c)
|
userContext := h.Hooks.UseUserContext(c)
|
||||||
|
|
||||||
|
// If we are using basic auth, we need to check if the user has totp and if it does then disable basic auth
|
||||||
|
if userContext.Provider == "basic" && userContext.TotpEnabled {
|
||||||
|
log.Warn().Str("username", userContext.Username).Msg("User has totp enabled, disabling basic auth")
|
||||||
|
userContext.IsLoggedIn = false
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user is logged in
|
// Check if user is logged in
|
||||||
if userContext.IsLoggedIn {
|
if userContext.IsLoggedIn {
|
||||||
log.Debug().Msg("Authenticated")
|
log.Debug().Msg("Authenticated")
|
||||||
|
|
||||||
// Check if user is allowed to access subdomain, if request is nginx.example.com the subdomain (resource) is nginx
|
// Check if user is allowed to access subdomain, if request is nginx.example.com the subdomain (resource) is nginx
|
||||||
appAllowed, err := h.Auth.ResourceAllowed(c, userContext)
|
appAllowed := h.Auth.ResourceAllowed(c, userContext, labels)
|
||||||
|
|
||||||
// Check if there was an error
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("Failed to check if app is allowed")
|
|
||||||
|
|
||||||
if proxy.Proxy == "nginx" || !isBrowser {
|
|
||||||
c.JSON(500, gin.H{
|
|
||||||
"status": 500,
|
|
||||||
"message": "Internal Server Error",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug().Bool("appAllowed", appAllowed).Msg("Checking if app is allowed")
|
log.Debug().Bool("appAllowed", appAllowed).Msg("Checking if app is allowed")
|
||||||
|
|
||||||
@@ -154,9 +147,6 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
if !appAllowed {
|
if !appAllowed {
|
||||||
log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed")
|
log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User not allowed")
|
||||||
|
|
||||||
// Set WWW-Authenticate header
|
|
||||||
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
|
|
||||||
|
|
||||||
if proxy.Proxy == "nginx" || !isBrowser {
|
if proxy.Proxy == "nginx" || !isBrowser {
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"status": 401,
|
"status": 401,
|
||||||
@@ -165,11 +155,20 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build query
|
// Values
|
||||||
queries, err := query.Values(types.UnauthorizedQuery{
|
values := types.UnauthorizedQuery{
|
||||||
Username: userContext.Username,
|
|
||||||
Resource: strings.Split(host, ".")[0],
|
Resource: strings.Split(host, ".")[0],
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Use either username or email
|
||||||
|
if userContext.OAuth {
|
||||||
|
values.Username = userContext.Email
|
||||||
|
} else {
|
||||||
|
values.Username = userContext.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query
|
||||||
|
queries, err := query.Values(values)
|
||||||
|
|
||||||
// Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik)
|
// Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -183,13 +182,63 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the user header
|
// Check groups if using OAuth
|
||||||
c.Header("Remote-User", userContext.Username)
|
if userContext.OAuth {
|
||||||
|
// Check if user is in required groups
|
||||||
|
groupOk := h.Auth.OAuthGroup(c, userContext, labels)
|
||||||
|
|
||||||
|
log.Debug().Bool("groupOk", groupOk).Msg("Checking if user is in required groups")
|
||||||
|
|
||||||
|
// The user is not allowed to access the app
|
||||||
|
if !groupOk {
|
||||||
|
log.Warn().Str("username", userContext.Username).Str("host", host).Msg("User is not in required groups")
|
||||||
|
|
||||||
|
if proxy.Proxy == "nginx" || !isBrowser {
|
||||||
|
c.JSON(401, gin.H{
|
||||||
|
"status": 401,
|
||||||
|
"message": "Unauthorized",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Values
|
||||||
|
values := types.UnauthorizedQuery{
|
||||||
|
Resource: strings.Split(host, ".")[0],
|
||||||
|
GroupErr: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use either username or email
|
||||||
|
if userContext.OAuth {
|
||||||
|
values.Username = userContext.Email
|
||||||
|
} else {
|
||||||
|
values.Username = userContext.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query
|
||||||
|
queries, err := query.Values(values)
|
||||||
|
|
||||||
|
// Handle error (no need to check for nginx/headers since we are sure we are using caddy/traefik)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to build queries")
|
||||||
|
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are using caddy/traefik so redirect
|
||||||
|
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/unauthorized?%s", h.Config.AppURL, queries.Encode()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Header("Remote-User", utils.SanitizeHeader(userContext.Username))
|
||||||
|
c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name))
|
||||||
|
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
|
||||||
|
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
|
||||||
|
|
||||||
// Set the rest of the headers
|
// Set the rest of the headers
|
||||||
for key, value := range labels.Headers {
|
for key, value := range labels.Headers {
|
||||||
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
log.Debug().Str("key", key).Str("value", value).Msg("Setting header")
|
||||||
c.Header(key, value)
|
c.Header(key, utils.SanitizeHeader(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The user is allowed to access the app
|
// The user is allowed to access the app
|
||||||
@@ -203,9 +252,6 @@ func (h *Handlers) AuthHandler(c *gin.Context) {
|
|||||||
// The user is not logged in
|
// The user is not logged in
|
||||||
log.Debug().Msg("Unauthorized")
|
log.Debug().Msg("Unauthorized")
|
||||||
|
|
||||||
// Set www-authenticate header
|
|
||||||
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
|
|
||||||
|
|
||||||
if proxy.Proxy == "nginx" || !isBrowser {
|
if proxy.Proxy == "nginx" || !isBrowser {
|
||||||
c.JSON(401, gin.H{
|
c.JSON(401, gin.H{
|
||||||
"status": 401,
|
"status": 401,
|
||||||
@@ -310,6 +356,8 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
|
|||||||
// Set totp pending cookie
|
// Set totp pending cookie
|
||||||
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
||||||
Username: login.Username,
|
Username: login.Username,
|
||||||
|
Name: utils.Capitalize(login.Username),
|
||||||
|
Email: fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
|
||||||
Provider: "username",
|
Provider: "username",
|
||||||
TotpPending: true,
|
TotpPending: true,
|
||||||
})
|
})
|
||||||
@@ -328,6 +376,8 @@ func (h *Handlers) LoginHandler(c *gin.Context) {
|
|||||||
// Create session cookie with username as provider
|
// Create session cookie with username as provider
|
||||||
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
||||||
Username: login.Username,
|
Username: login.Username,
|
||||||
|
Name: utils.Capitalize(login.Username),
|
||||||
|
Email: fmt.Sprintf("%s@%s", strings.ToLower(login.Username), h.Config.Domain),
|
||||||
Provider: "username",
|
Provider: "username",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -402,6 +452,8 @@ func (h *Handlers) TotpHandler(c *gin.Context) {
|
|||||||
// Create session cookie with username as provider
|
// Create session cookie with username as provider
|
||||||
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
|
Name: utils.Capitalize(user.Username),
|
||||||
|
Email: fmt.Sprintf("%s@%s", strings.ToLower(user.Username), h.Config.Domain),
|
||||||
Provider: "username",
|
Provider: "username",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -449,6 +501,7 @@ func (h *Handlers) AppHandler(c *gin.Context) {
|
|||||||
Domain: h.Config.Domain,
|
Domain: h.Config.Domain,
|
||||||
ForgotPasswordMessage: h.Config.ForgotPasswordMessage,
|
ForgotPasswordMessage: h.Config.ForgotPasswordMessage,
|
||||||
BackgroundImage: h.Config.BackgroundImage,
|
BackgroundImage: h.Config.BackgroundImage,
|
||||||
|
OAuthAutoRedirect: h.Config.OAuthAutoRedirect,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return app context
|
// Return app context
|
||||||
@@ -466,15 +519,16 @@ func (h *Handlers) UserHandler(c *gin.Context) {
|
|||||||
Status: 200,
|
Status: 200,
|
||||||
IsLoggedIn: userContext.IsLoggedIn,
|
IsLoggedIn: userContext.IsLoggedIn,
|
||||||
Username: userContext.Username,
|
Username: userContext.Username,
|
||||||
|
Name: userContext.Name,
|
||||||
|
Email: userContext.Email,
|
||||||
Provider: userContext.Provider,
|
Provider: userContext.Provider,
|
||||||
Oauth: userContext.OAuth,
|
Oauth: userContext.OAuth,
|
||||||
TotpPending: userContext.TotpPending,
|
TotpPending: userContext.TotpPending,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are not logged in we set the status to 401 and add the WWW-Authenticate header else we set it to 200
|
// If we are not logged in we set the status to 401 else we set it to 200
|
||||||
if !userContext.IsLoggedIn {
|
if !userContext.IsLoggedIn {
|
||||||
log.Debug().Msg("Unauthorized")
|
log.Debug().Msg("Unauthorized")
|
||||||
c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"")
|
|
||||||
userContextResponse.Message = "Unauthorized"
|
userContextResponse.Message = "Unauthorized"
|
||||||
} else {
|
} else {
|
||||||
log.Debug().Interface("userContext", userContext).Msg("Authenticated")
|
log.Debug().Interface("userContext", userContext).Msg("Authenticated")
|
||||||
@@ -614,25 +668,32 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get email
|
// Get user
|
||||||
email, err := h.Providers.GetUser(providerName.Provider)
|
user, err := h.Providers.GetUser(providerName.Provider)
|
||||||
|
|
||||||
log.Debug().Str("email", email).Msg("Got email")
|
|
||||||
|
|
||||||
// Handle error
|
// Handle error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get email")
|
log.Error().Msg("Failed to get user")
|
||||||
|
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("Got user")
|
||||||
|
|
||||||
|
// Check that email is not empty
|
||||||
|
if user.Email == "" {
|
||||||
|
log.Error().Msg("Email is empty")
|
||||||
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
|
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/error", h.Config.AppURL))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email is not whitelisted
|
// Email is not whitelisted
|
||||||
if !h.Auth.EmailWhitelisted(email) {
|
if !h.Auth.EmailWhitelisted(user.Email) {
|
||||||
log.Warn().Str("email", email).Msg("Email not whitelisted")
|
log.Warn().Str("email", user.Email).Msg("Email not whitelisted")
|
||||||
|
|
||||||
// Build query
|
// Build query
|
||||||
queries, err := query.Values(types.UnauthorizedQuery{
|
queries, err := query.Values(types.UnauthorizedQuery{
|
||||||
Username: email,
|
Username: user.Email,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle error
|
// Handle error
|
||||||
@@ -648,10 +709,31 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
|
|||||||
|
|
||||||
log.Debug().Msg("Email whitelisted")
|
log.Debug().Msg("Email whitelisted")
|
||||||
|
|
||||||
|
// Get username
|
||||||
|
var username string
|
||||||
|
|
||||||
|
if user.PreferredUsername != "" {
|
||||||
|
username = user.PreferredUsername
|
||||||
|
} else {
|
||||||
|
username = fmt.Sprintf("%s_%s", strings.Split(user.Email, "@")[0], strings.Split(user.Email, "@")[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get name
|
||||||
|
var name string
|
||||||
|
|
||||||
|
if user.Name != "" {
|
||||||
|
name = user.Name
|
||||||
|
} else {
|
||||||
|
name = fmt.Sprintf("%s (%s)", utils.Capitalize(strings.Split(user.Email, "@")[0]), strings.Split(user.Email, "@")[1])
|
||||||
|
}
|
||||||
|
|
||||||
// Create session cookie (also cleans up redirect cookie)
|
// Create session cookie (also cleans up redirect cookie)
|
||||||
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
h.Auth.CreateSessionCookie(c, &types.SessionCookie{
|
||||||
Username: email,
|
Username: username,
|
||||||
Provider: providerName.Provider,
|
Name: name,
|
||||||
|
Email: user.Email,
|
||||||
|
Provider: providerName.Provider,
|
||||||
|
OAuthGroups: strings.Join(user.Groups, ","),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if we have a redirect URI
|
// Check if we have a redirect URI
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
package hooks
|
package hooks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
"tinyauth/internal/auth"
|
"tinyauth/internal/auth"
|
||||||
"tinyauth/internal/providers"
|
"tinyauth/internal/providers"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
|
"tinyauth/internal/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewHooks(auth *auth.Auth, providers *providers.Providers) *Hooks {
|
func NewHooks(config types.HooksConfig, auth *auth.Auth, providers *providers.Providers) *Hooks {
|
||||||
return &Hooks{
|
return &Hooks{
|
||||||
|
Config: config,
|
||||||
Auth: auth,
|
Auth: auth,
|
||||||
Providers: providers,
|
Providers: providers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hooks struct {
|
type Hooks struct {
|
||||||
|
Config types.HooksConfig
|
||||||
Auth *auth.Auth
|
Auth *auth.Auth
|
||||||
Providers *providers.Providers
|
Providers *providers.Providers
|
||||||
}
|
}
|
||||||
@@ -30,17 +35,27 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
if basic != nil {
|
if basic != nil {
|
||||||
log.Debug().Msg("Got basic auth")
|
log.Debug().Msg("Got basic auth")
|
||||||
|
|
||||||
// Check if user exists and password is correct
|
// Get user
|
||||||
user := hooks.Auth.GetUser(basic.Username)
|
user := hooks.Auth.GetUser(basic.Username)
|
||||||
|
|
||||||
if user != nil && hooks.Auth.CheckPassword(*user, basic.Password) {
|
// Check we have a user
|
||||||
|
if user == nil {
|
||||||
|
log.Error().Str("username", basic.Username).Msg("User does not exist")
|
||||||
|
|
||||||
|
// Return empty context
|
||||||
|
return types.UserContext{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user has a correct password
|
||||||
|
if hooks.Auth.CheckPassword(*user, basic.Password) {
|
||||||
// Return user context since we are logged in with basic auth
|
// Return user context since we are logged in with basic auth
|
||||||
return types.UserContext{
|
return types.UserContext{
|
||||||
Username: basic.Username,
|
Username: basic.Username,
|
||||||
|
Name: utils.Capitalize(basic.Username),
|
||||||
|
Email: fmt.Sprintf("%s@%s", strings.ToLower(basic.Username), hooks.Config.Domain),
|
||||||
IsLoggedIn: true,
|
IsLoggedIn: true,
|
||||||
OAuth: false,
|
|
||||||
Provider: "basic",
|
Provider: "basic",
|
||||||
TotpPending: false,
|
TotpEnabled: user.TotpSecret != "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,13 +65,7 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get session cookie")
|
log.Error().Err(err).Msg("Failed to get session cookie")
|
||||||
// Return empty context
|
// Return empty context
|
||||||
return types.UserContext{
|
return types.UserContext{}
|
||||||
Username: "",
|
|
||||||
IsLoggedIn: false,
|
|
||||||
OAuth: false,
|
|
||||||
Provider: "",
|
|
||||||
TotpPending: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if session cookie has totp pending
|
// Check if session cookie has totp pending
|
||||||
@@ -65,8 +74,8 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
// Return empty context since we are pending totp
|
// Return empty context since we are pending totp
|
||||||
return types.UserContext{
|
return types.UserContext{
|
||||||
Username: cookie.Username,
|
Username: cookie.Username,
|
||||||
IsLoggedIn: false,
|
Name: cookie.Name,
|
||||||
OAuth: false,
|
Email: cookie.Email,
|
||||||
Provider: cookie.Provider,
|
Provider: cookie.Provider,
|
||||||
TotpPending: true,
|
TotpPending: true,
|
||||||
}
|
}
|
||||||
@@ -82,11 +91,11 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
|
|
||||||
// It exists so we are logged in
|
// It exists so we are logged in
|
||||||
return types.UserContext{
|
return types.UserContext{
|
||||||
Username: cookie.Username,
|
Username: cookie.Username,
|
||||||
IsLoggedIn: true,
|
Name: cookie.Name,
|
||||||
OAuth: false,
|
Email: cookie.Email,
|
||||||
Provider: "username",
|
IsLoggedIn: true,
|
||||||
TotpPending: false,
|
Provider: "username",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,20 +110,14 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
log.Debug().Msg("Provider exists")
|
log.Debug().Msg("Provider exists")
|
||||||
|
|
||||||
// Check if the oauth email is whitelisted
|
// Check if the oauth email is whitelisted
|
||||||
if !hooks.Auth.EmailWhitelisted(cookie.Username) {
|
if !hooks.Auth.EmailWhitelisted(cookie.Email) {
|
||||||
log.Error().Str("email", cookie.Username).Msg("Email is not whitelisted")
|
log.Error().Str("email", cookie.Email).Msg("Email is not whitelisted")
|
||||||
|
|
||||||
// It isn't so we delete the cookie and return an empty context
|
// It isn't so we delete the cookie and return an empty context
|
||||||
hooks.Auth.DeleteSessionCookie(c)
|
hooks.Auth.DeleteSessionCookie(c)
|
||||||
|
|
||||||
// Return empty context
|
// Return empty context
|
||||||
return types.UserContext{
|
return types.UserContext{}
|
||||||
Username: "",
|
|
||||||
IsLoggedIn: false,
|
|
||||||
OAuth: false,
|
|
||||||
Provider: "",
|
|
||||||
TotpPending: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Email is whitelisted")
|
log.Debug().Msg("Email is whitelisted")
|
||||||
@@ -122,19 +125,15 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) types.UserContext {
|
|||||||
// Return user context since we are logged in with oauth
|
// Return user context since we are logged in with oauth
|
||||||
return types.UserContext{
|
return types.UserContext{
|
||||||
Username: cookie.Username,
|
Username: cookie.Username,
|
||||||
|
Name: cookie.Name,
|
||||||
|
Email: cookie.Email,
|
||||||
IsLoggedIn: true,
|
IsLoggedIn: true,
|
||||||
OAuth: true,
|
OAuth: true,
|
||||||
Provider: cookie.Provider,
|
Provider: cookie.Provider,
|
||||||
TotpPending: false,
|
OAuthGroups: cookie.OAuthGroups,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neither basic auth or oauth is set so we return an empty context
|
// Neither basic auth or oauth is set so we return an empty context
|
||||||
return types.UserContext{
|
return types.UserContext{}
|
||||||
Username: "",
|
|
||||||
IsLoggedIn: false,
|
|
||||||
OAuth: false,
|
|
||||||
Provider: "",
|
|
||||||
TotpPending: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,25 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"tinyauth/internal/constants"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// We are assuming that the generic provider will return a JSON object with an email field
|
func GetGenericUser(client *http.Client, url string) (constants.Claims, error) {
|
||||||
type GenericUserInfoResponse struct {
|
// Create user struct
|
||||||
Email string `json:"email"`
|
var user constants.Claims
|
||||||
}
|
|
||||||
|
|
||||||
func GetGenericEmail(client *http.Client, url string) (string, error) {
|
|
||||||
// Using the oauth client get the user info url
|
// Using the oauth client get the user info url
|
||||||
res, err := client.Get(url)
|
res, err := client.Get(url)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
log.Debug().Msg("Got response from generic provider")
|
log.Debug().Msg("Got response from generic provider")
|
||||||
|
|
||||||
// Read the body of the response
|
// Read the body of the response
|
||||||
@@ -29,24 +30,21 @@ func GetGenericEmail(client *http.Client, url string) (string, error) {
|
|||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Read body from generic provider")
|
log.Debug().Msg("Read body from generic provider")
|
||||||
|
|
||||||
// Parse the body into a user struct
|
|
||||||
var user GenericUserInfoResponse
|
|
||||||
|
|
||||||
// Unmarshal the body into the user struct
|
// Unmarshal the body into the user struct
|
||||||
err = json.Unmarshal(body, &user)
|
err = json.Unmarshal(body, &user)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Parsed user from generic provider")
|
log.Debug().Msg("Parsed user from generic provider")
|
||||||
|
|
||||||
// Return the email
|
// Return the user
|
||||||
return user.Email, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,51 +5,96 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"tinyauth/internal/constants"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Github has a different response than the generic provider
|
// Response for the github email endpoint
|
||||||
type GithubUserInfoResponse []struct {
|
type GithubEmailResponse []struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The scopes required for the github provider
|
// Response for the github user endpoint
|
||||||
func GithubScopes() []string {
|
type GithubUserInfoResponse struct {
|
||||||
return []string{"user:email"}
|
Login string `json:"login"`
|
||||||
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGithubEmail(client *http.Client) (string, error) {
|
// The scopes required for the github provider
|
||||||
// Get the user emails from github using the oauth http client
|
func GithubScopes() []string {
|
||||||
res, err := client.Get("https://api.github.com/user/emails")
|
return []string{"user:email", "read:user"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGithubUser(client *http.Client) (constants.Claims, error) {
|
||||||
|
// Create user struct
|
||||||
|
var user constants.Claims
|
||||||
|
|
||||||
|
// Get the user info from github using the oauth http client
|
||||||
|
res, err := client.Get("https://api.github.com/user")
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got response from github")
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
log.Debug().Msg("Got user response from github")
|
||||||
|
|
||||||
// Read the body of the response
|
// Read the body of the response
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Read body from github")
|
log.Debug().Msg("Read user body from github")
|
||||||
|
|
||||||
// Parse the body into a user struct
|
// Parse the body into a user struct
|
||||||
var emails GithubUserInfoResponse
|
var userInfo GithubUserInfoResponse
|
||||||
|
|
||||||
|
// Unmarshal the body into the user struct
|
||||||
|
err = json.Unmarshal(body, &userInfo)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the user emails from github using the oauth http client
|
||||||
|
res, err = client.Get("https://api.github.com/user/emails")
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
log.Debug().Msg("Got email response from github")
|
||||||
|
|
||||||
|
// Read the body of the response
|
||||||
|
body, err = io.ReadAll(res.Body)
|
||||||
|
|
||||||
|
// Check if there was an error
|
||||||
|
if err != nil {
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msg("Read email body from github")
|
||||||
|
|
||||||
|
// Parse the body into a user struct
|
||||||
|
var emails GithubEmailResponse
|
||||||
|
|
||||||
// Unmarshal the body into the user struct
|
// Unmarshal the body into the user struct
|
||||||
err = json.Unmarshal(body, &emails)
|
err = json.Unmarshal(body, &emails)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Parsed emails from github")
|
log.Debug().Msg("Parsed emails from github")
|
||||||
@@ -57,10 +102,28 @@ func GetGithubEmail(client *http.Client) (string, error) {
|
|||||||
// Find and return the primary email
|
// Find and return the primary email
|
||||||
for _, email := range emails {
|
for _, email := range emails {
|
||||||
if email.Primary {
|
if email.Primary {
|
||||||
return email.Email, nil
|
// Set the email then exit
|
||||||
|
log.Debug().Str("email", email.Email).Msg("Found primary email")
|
||||||
|
user.Email = email.Email
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// User does not have a primary email?
|
// If no primary email was found, use the first available email
|
||||||
return "", errors.New("no primary email found")
|
if len(emails) == 0 {
|
||||||
|
return user, errors.New("no emails found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the email if it is not set picking the first one
|
||||||
|
if user.Email == "" {
|
||||||
|
log.Warn().Str("email", emails[0].Email).Msg("No primary email found, using first email")
|
||||||
|
user.Email = emails[0].Email
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the username and name
|
||||||
|
user.PreferredUsername = userInfo.Login
|
||||||
|
user.Name = userInfo.Name
|
||||||
|
|
||||||
|
// Return
|
||||||
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,29 +4,37 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"tinyauth/internal/constants"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Google works the same as the generic provider
|
// Response for the google user endpoint
|
||||||
type GoogleUserInfoResponse struct {
|
type GoogleUserInfoResponse struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The scopes required for the google provider
|
// The scopes required for the google provider
|
||||||
func GoogleScopes() []string {
|
func GoogleScopes() []string {
|
||||||
return []string{"https://www.googleapis.com/auth/userinfo.email"}
|
return []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetGoogleEmail(client *http.Client) (string, error) {
|
func GetGoogleUser(client *http.Client) (constants.Claims, error) {
|
||||||
|
// Create user struct
|
||||||
|
var user constants.Claims
|
||||||
|
|
||||||
// Get the user info from google using the oauth http client
|
// Get the user info from google using the oauth http client
|
||||||
res, err := client.Get("https://www.googleapis.com/userinfo/v2/me")
|
res, err := client.Get("https://www.googleapis.com/userinfo/v2/me")
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
log.Debug().Msg("Got response from google")
|
log.Debug().Msg("Got response from google")
|
||||||
|
|
||||||
// Read the body of the response
|
// Read the body of the response
|
||||||
@@ -34,24 +42,29 @@ func GetGoogleEmail(client *http.Client) (string, error) {
|
|||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Read body from google")
|
log.Debug().Msg("Read body from google")
|
||||||
|
|
||||||
// Parse the body into a user struct
|
// Create a new user info struct
|
||||||
var user GoogleUserInfoResponse
|
var userInfo GoogleUserInfoResponse
|
||||||
|
|
||||||
// Unmarshal the body into the user struct
|
// Unmarshal the body into the user struct
|
||||||
err = json.Unmarshal(body, &user)
|
err = json.Unmarshal(body, &userInfo)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Parsed user from google")
|
log.Debug().Msg("Parsed user from google")
|
||||||
|
|
||||||
// Return the email
|
// Map the user info to the user struct
|
||||||
return user.Email, nil
|
user.PreferredUsername = strings.Split(userInfo.Email, "@")[0]
|
||||||
|
user.Name = userInfo.Name
|
||||||
|
user.Email = userInfo.Email
|
||||||
|
|
||||||
|
// Return the user
|
||||||
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package providers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"tinyauth/internal/constants"
|
||||||
"tinyauth/internal/oauth"
|
"tinyauth/internal/oauth"
|
||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
|
|
||||||
@@ -93,14 +94,17 @@ func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (providers *Providers) GetUser(provider string) (string, error) {
|
func (providers *Providers) GetUser(provider string) (constants.Claims, error) {
|
||||||
// Get the email from the provider
|
// Create user struct
|
||||||
|
var user constants.Claims
|
||||||
|
|
||||||
|
// Get the user from the provider
|
||||||
switch provider {
|
switch provider {
|
||||||
case "github":
|
case "github":
|
||||||
// If the github provider is not configured, return an error
|
// If the github provider is not configured, return an error
|
||||||
if providers.Github == nil {
|
if providers.Github == nil {
|
||||||
log.Debug().Msg("Github provider not configured")
|
log.Debug().Msg("Github provider not configured")
|
||||||
return "", nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the client from the github provider
|
// Get the client from the github provider
|
||||||
@@ -108,23 +112,23 @@ func (providers *Providers) GetUser(provider string) (string, error) {
|
|||||||
|
|
||||||
log.Debug().Msg("Got client from github")
|
log.Debug().Msg("Got client from github")
|
||||||
|
|
||||||
// Get the email from the github provider
|
// Get the user from the github provider
|
||||||
email, err := GetGithubEmail(client)
|
user, err := GetGithubUser(client)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got email from github")
|
log.Debug().Msg("Got user from github")
|
||||||
|
|
||||||
// Return the email
|
// Return the user
|
||||||
return email, nil
|
return user, nil
|
||||||
case "google":
|
case "google":
|
||||||
// If the google provider is not configured, return an error
|
// If the google provider is not configured, return an error
|
||||||
if providers.Google == nil {
|
if providers.Google == nil {
|
||||||
log.Debug().Msg("Google provider not configured")
|
log.Debug().Msg("Google provider not configured")
|
||||||
return "", nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the client from the google provider
|
// Get the client from the google provider
|
||||||
@@ -132,23 +136,23 @@ func (providers *Providers) GetUser(provider string) (string, error) {
|
|||||||
|
|
||||||
log.Debug().Msg("Got client from google")
|
log.Debug().Msg("Got client from google")
|
||||||
|
|
||||||
// Get the email from the google provider
|
// Get the user from the google provider
|
||||||
email, err := GetGoogleEmail(client)
|
user, err := GetGoogleUser(client)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got email from google")
|
log.Debug().Msg("Got user from google")
|
||||||
|
|
||||||
// Return the email
|
// Return the user
|
||||||
return email, nil
|
return user, nil
|
||||||
case "generic":
|
case "generic":
|
||||||
// If the generic provider is not configured, return an error
|
// If the generic provider is not configured, return an error
|
||||||
if providers.Generic == nil {
|
if providers.Generic == nil {
|
||||||
log.Debug().Msg("Generic provider not configured")
|
log.Debug().Msg("Generic provider not configured")
|
||||||
return "", nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the client from the generic provider
|
// Get the client from the generic provider
|
||||||
@@ -156,20 +160,20 @@ func (providers *Providers) GetUser(provider string) (string, error) {
|
|||||||
|
|
||||||
log.Debug().Msg("Got client from generic")
|
log.Debug().Msg("Got client from generic")
|
||||||
|
|
||||||
// Get the email from the generic provider
|
// Get the user from the generic provider
|
||||||
email, err := GetGenericEmail(client, providers.Config.GenericUserURL)
|
user, err := GetGenericUser(client, providers.Config.GenericUserURL)
|
||||||
|
|
||||||
// Check if there was an error
|
// Check if there was an error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Got email from generic")
|
log.Debug().Msg("Got user from generic")
|
||||||
|
|
||||||
// Return the email
|
// Return the email
|
||||||
return email, nil
|
return user, nil
|
||||||
default:
|
default:
|
||||||
return "", nil
|
return user, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type OAuthRequest struct {
|
|||||||
type UnauthorizedQuery struct {
|
type UnauthorizedQuery struct {
|
||||||
Username string `url:"username"`
|
Username string `url:"username"`
|
||||||
Resource string `url:"resource"`
|
Resource string `url:"resource"`
|
||||||
|
GroupErr bool `url:"groupErr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy is the uri parameters for the proxy endpoint
|
// Proxy is the uri parameters for the proxy endpoint
|
||||||
@@ -33,6 +34,8 @@ type UserContextResponse struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
IsLoggedIn bool `json:"isLoggedIn"`
|
IsLoggedIn bool `json:"isLoggedIn"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
Oauth bool `json:"oauth"`
|
Oauth bool `json:"oauth"`
|
||||||
TotpPending bool `json:"totpPending"`
|
TotpPending bool `json:"totpPending"`
|
||||||
@@ -49,6 +52,7 @@ type AppContext struct {
|
|||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
ForgotPasswordMessage string `json:"forgotPasswordMessage"`
|
ForgotPasswordMessage string `json:"forgotPasswordMessage"`
|
||||||
BackgroundImage string `json:"backgroundImage"`
|
BackgroundImage string `json:"backgroundImage"`
|
||||||
|
OAuthAutoRedirect string `json:"oauthAutoRedirect"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Totp request is the request for the totp endpoint
|
// Totp request is the request for the totp endpoint
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type Config struct {
|
|||||||
GenericName string `mapstructure:"generic-name"`
|
GenericName string `mapstructure:"generic-name"`
|
||||||
DisableContinue bool `mapstructure:"disable-continue"`
|
DisableContinue bool `mapstructure:"disable-continue"`
|
||||||
OAuthWhitelist string `mapstructure:"oauth-whitelist"`
|
OAuthWhitelist string `mapstructure:"oauth-whitelist"`
|
||||||
|
OAuthAutoRedirect string `mapstructure:"oauth-auto-redirect" validate:"oneof=none github google generic"`
|
||||||
SessionExpiry int `mapstructure:"session-expiry"`
|
SessionExpiry int `mapstructure:"session-expiry"`
|
||||||
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
|
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
|
||||||
Title string `mapstructure:"app-title"`
|
Title string `mapstructure:"app-title"`
|
||||||
@@ -46,6 +47,7 @@ type HandlersConfig struct {
|
|||||||
Title string
|
Title string
|
||||||
ForgotPasswordMessage string
|
ForgotPasswordMessage string
|
||||||
BackgroundImage string
|
BackgroundImage string
|
||||||
|
OAuthAutoRedirect string
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuthConfig is the configuration for the providers
|
// OAuthConfig is the configuration for the providers
|
||||||
@@ -80,3 +82,8 @@ type AuthConfig struct {
|
|||||||
LoginTimeout int
|
LoginTimeout int
|
||||||
LoginMaxRetries int
|
LoginMaxRetries int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HooksConfig is the configuration for the hooks service
|
||||||
|
type HooksConfig struct {
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,8 +25,11 @@ type OAuthProviders struct {
|
|||||||
// SessionCookie is the cookie for the session (exculding the expiry)
|
// SessionCookie is the cookie for the session (exculding the expiry)
|
||||||
type SessionCookie struct {
|
type SessionCookie struct {
|
||||||
Username string
|
Username string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
Provider string
|
Provider string
|
||||||
TotpPending bool
|
TotpPending bool
|
||||||
|
OAuthGroups string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TinyauthLabels is the labels for the tinyauth container
|
// TinyauthLabels is the labels for the tinyauth container
|
||||||
@@ -35,15 +38,20 @@ type TinyauthLabels struct {
|
|||||||
Users string
|
Users string
|
||||||
Allowed string
|
Allowed string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
|
OAuthGroups string
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserContext is the context for the user
|
// UserContext is the context for the user
|
||||||
type UserContext struct {
|
type UserContext struct {
|
||||||
Username string
|
Username string
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
IsLoggedIn bool
|
IsLoggedIn bool
|
||||||
OAuth bool
|
OAuth bool
|
||||||
Provider string
|
Provider string
|
||||||
TotpPending bool
|
TotpPending bool
|
||||||
|
OAuthGroups string
|
||||||
|
TotpEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginAttempt tracks information about login attempts for rate limiting
|
// LoginAttempt tracks information about login attempts for rate limiting
|
||||||
|
|||||||
@@ -204,6 +204,8 @@ func GetTinyauthLabels(labels map[string]string) types.TinyauthLabels {
|
|||||||
}
|
}
|
||||||
tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1]
|
tinyauthLabels.Headers[headerSplit[0]] = headerSplit[1]
|
||||||
}
|
}
|
||||||
|
case "tinyauth.oauth.groups":
|
||||||
|
tinyauthLabels.OAuthGroups = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -323,3 +325,22 @@ func CheckWhitelist(whitelist string, str string) bool {
|
|||||||
// Return false if no match was found
|
// Return false if no match was found
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capitalize just the first letter of a string
|
||||||
|
func Capitalize(str string) string {
|
||||||
|
if len(str) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return strings.ToUpper(string([]rune(str)[0])) + string([]rune(str)[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize header removes all control characters from a string
|
||||||
|
func SanitizeHeader(header string) string {
|
||||||
|
return strings.Map(func(r rune) rune {
|
||||||
|
// Allow only printable ASCII characters (32-126) and safe whitespace (space, tab)
|
||||||
|
if r == ' ' || r == '\t' || (r >= 32 && r <= 126) {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}, header)
|
||||||
|
}
|
||||||
|
|||||||
@@ -467,3 +467,65 @@ func TestCheckWhitelist(t *testing.T) {
|
|||||||
t.Fatalf("Expected %v, got %v", expected, result)
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test capitalize
|
||||||
|
func TestCapitalize(t *testing.T) {
|
||||||
|
t.Log("Testing capitalize with a valid string")
|
||||||
|
|
||||||
|
// Create variables
|
||||||
|
str := "test"
|
||||||
|
expected := "Test"
|
||||||
|
|
||||||
|
// Test the capitalize function
|
||||||
|
result := utils.Capitalize(str)
|
||||||
|
|
||||||
|
// Check if the result is equal to the expected
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("Testing capitalize with an empty string")
|
||||||
|
|
||||||
|
// Create variables
|
||||||
|
str = ""
|
||||||
|
expected = ""
|
||||||
|
|
||||||
|
// Test the capitalize function
|
||||||
|
result = utils.Capitalize(str)
|
||||||
|
|
||||||
|
// Check if the result is equal to the expected
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the header sanitizer
|
||||||
|
func TestSanitizeHeader(t *testing.T) {
|
||||||
|
t.Log("Testing sanitize header with a valid string")
|
||||||
|
|
||||||
|
// Create variables
|
||||||
|
str := "X-Header=value"
|
||||||
|
expected := "X-Header=value"
|
||||||
|
|
||||||
|
// Test the sanitize header function
|
||||||
|
result := utils.SanitizeHeader(str)
|
||||||
|
|
||||||
|
// Check if the result is equal to the expected
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("Testing sanitize header with an invalid string")
|
||||||
|
|
||||||
|
// Create variables
|
||||||
|
str = "X-Header=val\nue"
|
||||||
|
expected = "X-Header=value"
|
||||||
|
|
||||||
|
// Test the sanitize header function
|
||||||
|
result = utils.SanitizeHeader(str)
|
||||||
|
|
||||||
|
// Check if the result is equal to the expected
|
||||||
|
if result != expected {
|
||||||
|
t.Fatalf("Expected %v, got %v", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user