mirror of
https://github.com/tinyauthapp/tinyauth.git
synced 2026-09-17 23:13:34 +08:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9125e76596 | ||
|
|
653b747829 | ||
|
|
ba79a30f2e | ||
|
|
8c5094bbcd | ||
|
|
c28d9fe55e | ||
|
|
f58a6dccf5 | ||
|
|
0cf64e8598 |
@@ -81,7 +81,6 @@ A huge thank you to [selfh.st](https://selfh.st) for their generous donation to
|
||||
|
||||
- [JetBrains for Open-Source](https://jb.gg/OpenSource)
|
||||
- [CodeRabbit AI](https://www.coderabbit.ai)
|
||||
- [InstaPods](https://instapods.com) - [Deploy Tinyauth from 3$/month](https://app.instapods.com/dashboard/pods/create?app=tinyauth&ref=tinyauth)
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
"axios": "^1.20.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^26.4.0",
|
||||
"i18next": "^26.4.2",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"i18next-resources-to-backend": "^1.2.3",
|
||||
"lucide-react": "^1.38.0",
|
||||
"lucide-react": "^1.40.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"radix-ui": "^1.6.7",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
"react-hook-form": "^7.87.0",
|
||||
"react-i18next": "^17.0.12",
|
||||
"react-i18next": "^17.0.13",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router": "^8.3.1",
|
||||
"sonner": "^2.0.8",
|
||||
@@ -43,18 +43,18 @@
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@tanstack/eslint-plugin-query": "^5.102.8",
|
||||
"@types/node": "^26.4.0",
|
||||
"@types/node": "^26.4.1",
|
||||
"@types/react": "^19.2.18",
|
||||
"@types/react-dom": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.7",
|
||||
"@vitejs/plugin-react": "^6.1.1",
|
||||
"eslint": "^10.9.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.5",
|
||||
"globals": "^17.11.0",
|
||||
"eslint-plugin-react-refresh": "^0.5.6",
|
||||
"globals": "^17.12.0",
|
||||
"rollup-plugin-visualizer": "^7.1.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~6.0.2",
|
||||
"typescript-eslint": "^8.68.0",
|
||||
"typescript-eslint": "^8.69.0",
|
||||
"vite": "^8.2.2"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+408
-320
File diff suppressed because it is too large
Load Diff
@@ -1,104 +1,104 @@
|
||||
{
|
||||
"loginTitle": "Welcome back, login with",
|
||||
"loginTitleSimple": "Welcome back, please login",
|
||||
"loginDivider": "Or",
|
||||
"loginUsername": "Username",
|
||||
"loginPassword": "Password",
|
||||
"loginSubmit": "Login",
|
||||
"loginFailTitle": "Failed to log in",
|
||||
"loginFailSubtitle": "Please check your username and password",
|
||||
"loginFailRateLimit": "You failed to login too many times. Please try again later",
|
||||
"loginSuccessTitle": "Logged in",
|
||||
"loginSuccessSubtitle": "Welcome back!",
|
||||
"loginOauthFailTitle": "An error occurred",
|
||||
"loginOauthFailSubtitle": "Failed to get OAuth URL",
|
||||
"loginOauthSuccessTitle": "Redirecting",
|
||||
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
|
||||
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
|
||||
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
|
||||
"loginOauthAutoRedirectButton": "Redirect now",
|
||||
"continueTitle": "Continue",
|
||||
"continueRedirectingTitle": "Redirecting...",
|
||||
"continueRedirectingSubtitle": "You should be redirected to the app soon",
|
||||
"continueRedirectManually": "Redirect me manually",
|
||||
"continueInsecureRedirectTitle": "Insecure redirect",
|
||||
"continueInsecureRedirectSubtitle": "You are trying to redirect from <code>https</code> to <code>http</code> which is not secure. Are you sure you want to continue?",
|
||||
"continueUntrustedRedirectTitle": "Untrusted redirect",
|
||||
"continueUntrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain (<code>{{cookieDomain}}</code>). Are you sure you want to continue?",
|
||||
"logoutFailTitle": "Failed to log out",
|
||||
"logoutFailSubtitle": "Please try again",
|
||||
"logoutSuccessTitle": "Logged out",
|
||||
"logoutSuccessSubtitle": "You have been logged out",
|
||||
"logoutTitle": "Logout",
|
||||
"logoutUsernameSubtitle": "You are currently logged in as <code>{{username}}</code>. Click the button below to logout.",
|
||||
"logoutOauthSubtitle": "You are currently logged in as <code>{{username}}</code> using the {{provider}} OAuth provider. Click the button below to logout.",
|
||||
"notFoundTitle": "Page not found",
|
||||
"notFoundSubtitle": "The page you are looking for does not exist.",
|
||||
"notFoundButton": "Go home",
|
||||
"totpFailTitle": "Failed to verify code",
|
||||
"totpFailSubtitle": "Please check your code and try again",
|
||||
"totpSuccessTitle": "Verified",
|
||||
"totpSuccessSubtitle": "Redirecting to your app",
|
||||
"totpTitle": "Enter your TOTP code",
|
||||
"totpSubtitle": "Please enter the code from your authenticator app.",
|
||||
"unauthorizedTitle": "Unauthorized",
|
||||
"unauthorizedResourceSubtitle": "The user with username <code>{{username}}</code> is not authorized to access the resource <code>{{resource}}</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>.",
|
||||
"unauthorizedIpSubtitle": "Your IP address <code>{{ip}}</code> is not authorized to access the resource <code>{{resource}}</code>.",
|
||||
"unauthorizedButton": "Try again",
|
||||
"cancelTitle": "Cancel",
|
||||
"forgotPasswordTitle": "Forgot your password?",
|
||||
"failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.",
|
||||
"errorTitle": "An error occurred",
|
||||
"errorSubtitleInfo": "The following error occurred while processing your request:",
|
||||
"loginTitle": "Benvingut de nou, inicieu la sessió amb",
|
||||
"loginTitleSimple": "Benvingut de nou, si us plau, inicieu sessió",
|
||||
"loginDivider": "O",
|
||||
"loginUsername": "Nom d’usuari",
|
||||
"loginPassword": "Contrasenya",
|
||||
"loginSubmit": "Iniciar sessió",
|
||||
"loginFailTitle": "No s'ha pogut iniciar sessió",
|
||||
"loginFailSubtitle": "Si us plau, comproveu el vostre nom d'usuari i contrasenya",
|
||||
"loginFailRateLimit": "Heu fallat en iniciar sessió massa vegades. Si us plau, proveu-ho més tard",
|
||||
"loginSuccessTitle": "Heu iniciat sessió",
|
||||
"loginSuccessSubtitle": "Benvingut de nou!",
|
||||
"loginOauthFailTitle": "S'ha produït un error",
|
||||
"loginOauthFailSubtitle": "Errada en obtenir l'URL OAuth",
|
||||
"loginOauthSuccessTitle": "Redirigint",
|
||||
"loginOauthSuccessSubtitle": "Redirigint al vostre proveïdor OAuth",
|
||||
"loginOauthAutoRedirectTitle": "Autoredirecció OAuth",
|
||||
"loginOauthAutoRedirectSubtitle": "Sereu automàticament redirigits al vostre proveïdor OAuth per a autenticar-vos.",
|
||||
"loginOauthAutoRedirectButton": "Redirigeix ara",
|
||||
"continueTitle": "Continuar",
|
||||
"continueRedirectingTitle": "S'està redirigint...",
|
||||
"continueRedirectingSubtitle": "Hauríeu de ser redirigits aviat a l'app",
|
||||
"continueRedirectManually": "Redirigiu-me manualment",
|
||||
"continueInsecureRedirectTitle": "Redirecció no segura",
|
||||
"continueInsecureRedirectSubtitle": "Esteu intentant redirigir des de <code>https</code>a<code>http</code> que no és segur. Esteu segurs de voler continuar?",
|
||||
"continueUntrustedRedirectTitle": "Redirecció no confiable",
|
||||
"continueUntrustedRedirectSubtitle": "Esteu intentant redirigir a un domini que no coincideix amb el vostre domini (<code>{{cookieDomain}}</code>). Esteu segurs de voler continuar?",
|
||||
"logoutFailTitle": "Errada en tancar la sessió",
|
||||
"logoutFailSubtitle": "Si us plau, torneu-ho a provar",
|
||||
"logoutSuccessTitle": "Sessió tancada",
|
||||
"logoutSuccessSubtitle": "S'ha tancat la sessió",
|
||||
"logoutTitle": "Tanca la sessió",
|
||||
"logoutUsernameSubtitle": "Heu iniciat sessió com a <code>{{username}}</code>. Cliqueu el botó de sota per a tancar la sessió.",
|
||||
"logoutOauthSubtitle": "Heu iniciat la sessió actualment com a <code>{{username}}</code> fent servir {{provider}} com a proveïdor OAuth. Cliqueu el botó de sota per a tancar la sessió.",
|
||||
"notFoundTitle": "Pàgina no trobada",
|
||||
"notFoundSubtitle": "La pàgina que cerques no existeix.",
|
||||
"notFoundButton": "Torna a l'inici",
|
||||
"totpFailTitle": "Errada en verificar el codi",
|
||||
"totpFailSubtitle": "Si us plau, comprova el codi i torna-ho a provar",
|
||||
"totpSuccessTitle": "Verificat",
|
||||
"totpSuccessSubtitle": "Redirigint a la teva app",
|
||||
"totpTitle": "Introdueixi el codi TOTP",
|
||||
"totpSubtitle": "Si us plau, introdueix el codi de la teva aplicació d'autenticació.",
|
||||
"unauthorizedTitle": "No autoritzat",
|
||||
"unauthorizedResourceSubtitle": "L'usuari amb el nom <code>{{username}}</code> no està autoritzat a accedir al recurs <code>{{resource}}</code>.",
|
||||
"unauthorizedLoginSubtitle": "L'usuari amb el nom <code>{{username}}</code> no està autoritzat a iniciar la sessió.",
|
||||
"unauthorizedGroupsSubtitle": "L'usuari amb el nom <code>{{username}}</code> no està als grups requerits per al recurs <code>{{resource}}</code>.",
|
||||
"unauthorizedIpSubtitle": "La vostra adreça IP <code>{{ip}}</code> no està autoritzada a accedir al recurs <code>{{resource}}</code>.",
|
||||
"unauthorizedButton": "Tornar-ho a provar",
|
||||
"cancelTitle": "Cancel·lar",
|
||||
"forgotPasswordTitle": "Heu oblidat la vostra contrasenya?",
|
||||
"failedToFetchProvidersTitle": "Errada en carregar els proveïdors d'autenticació. Si us plau, verifiqueu la vostra configuració.",
|
||||
"errorTitle": "S'ha produït un error",
|
||||
"errorSubtitleInfo": "Ha ocorregut l'error següent mentre es processava la vostra petició:",
|
||||
"errorSubtitle": "An error occurred while trying to perform this action. Please check the console for more information.",
|
||||
"forgotPasswordMessage": "You can reset your password by changing the `USERS` environment variable.",
|
||||
"fieldRequired": "This field is required",
|
||||
"invalidInput": "Invalid input",
|
||||
"domainWarningTitle": "Invalid Domain",
|
||||
"domainWarningSubtitle": "You are accessing this instance from an incorrect domain. If you proceed, you may encounter issues with authentication.",
|
||||
"domainWarningCurrent": "Current:",
|
||||
"domainWarningExpected": "Expected:",
|
||||
"ignoreTitle": "Ignore",
|
||||
"goToCorrectDomainTitle": "Go to correct domain",
|
||||
"authorizeTitle": "Authorize",
|
||||
"authorizeCardTitle": "Continue to {{app}}?",
|
||||
"authorizeSubtitle": "Would you like to continue to this app? Please carefully review the permissions requested by the app.",
|
||||
"authorizeSubtitleOAuth": "Would you like to continue to this app?",
|
||||
"authorizeLoadingTitle": "Loading...",
|
||||
"authorizeLoadingSubtitle": "Please wait while we load the client information.",
|
||||
"authorizeSuccessTitle": "Authorized",
|
||||
"authorizeSuccessSubtitle": "You will be redirected to the app in a few seconds.",
|
||||
"authorizeErrorClientInfo": "An error occurred while loading the client information. Please try again later.",
|
||||
"authorizeErrorInvalidParams": "The request is missing required parameters or has invalid parameters. Please check the URL and try again.",
|
||||
"forgotPasswordMessage": "Podeu reiniciar la vostra contrasenya canviant la variable d'entorn `USERS`.",
|
||||
"fieldRequired": "Aquest camp és obligatori",
|
||||
"invalidInput": "Entrada no vàlida",
|
||||
"domainWarningTitle": "Domini invàlid",
|
||||
"domainWarningSubtitle": "Esteu visitant aquesta instància des d'un domini incorrecte. Si procediu, podríeu trobar-os amb problemes d'autenticació.",
|
||||
"domainWarningCurrent": "Actual:",
|
||||
"domainWarningExpected": "Esperat:",
|
||||
"ignoreTitle": "Ignorar",
|
||||
"goToCorrectDomainTitle": "Anar al domini correcte",
|
||||
"authorizeTitle": "Autoritzar",
|
||||
"authorizeCardTitle": "Continuar a {{app}}?",
|
||||
"authorizeSubtitle": "Voldríeu continuar a aquesta app? Si us plau, reviseu amb cura els permisos sol·licitats per a l'app.",
|
||||
"authorizeSubtitleOAuth": "Voldríeu continuar a aquesta app?",
|
||||
"authorizeLoadingTitle": "S'està carregant...",
|
||||
"authorizeLoadingSubtitle": "Si us plau, espereu mentre carreguem la informació del client.",
|
||||
"authorizeSuccessTitle": "Autoritzat",
|
||||
"authorizeSuccessSubtitle": "Sereu redirigits a l'app en pocs segons.",
|
||||
"authorizeErrorClientInfo": "Ha ocorregut un error mentre es carregava la informació del client. Si us plau, proveu-ho més tard.",
|
||||
"authorizeErrorInvalidParams": "La petició manca dels paràmetres requerits o té paràmetres invàlids. Si us plau, comproveu l'URL i torneu-ho a provar.",
|
||||
"openidScopeName": "OpenID Connect",
|
||||
"openidScopeDescription": "Allows the app to access your OpenID Connect information.",
|
||||
"emailScopeName": "Email",
|
||||
"emailScopeDescription": "Allows the app to access your email address.",
|
||||
"profileScopeName": "Profile",
|
||||
"profileScopeDescription": "Allows the app to access your profile information.",
|
||||
"groupsScopeName": "Groups",
|
||||
"groupsScopeDescription": "Allows the app to access your group information.",
|
||||
"backToLoginButton": "Back to login",
|
||||
"phoneScopeName": "Phone",
|
||||
"phoneScopeDescription": "Allows the app to access your phone number.",
|
||||
"addressScopeName": "Address",
|
||||
"addressScopeDescription": "Allows the app to access your address.",
|
||||
"loginTailscaleTitle": "Continue with Tailscale",
|
||||
"loginTailscaleDescription": "You appear to be accessing Tinyauth from an authorized Tailscale device. Would you like to continue with your Tailscale connection?",
|
||||
"loginTailscaleDeviceName": "Device name:",
|
||||
"loginTailscaleOtherMethod": "Login with another method",
|
||||
"loginTailscaleSuccess": "Successfully authenticated with Tailscale.",
|
||||
"loginTailscaleFail": "Failed to authenticate with Tailscale. Please try again or use another login method.",
|
||||
"logoutTailscaleSubtitle": "You are currently logged in with Tailscale on your device <code>{{deviceName}}</code>. Click the button below to logout.",
|
||||
"quickActionsLanguage": "Language",
|
||||
"quickActionsTheme": "Theme",
|
||||
"quickActionsThemeLight": "Light",
|
||||
"quickActionsThemeDark": "Dark",
|
||||
"quickActionsThemeSystem": "System",
|
||||
"quickActionsLogout": "Logout",
|
||||
"quickActionsTitle": "Quick Actions",
|
||||
"openidScopeDescription": "Permet a l'app l'accés a la vostra informació OpenID Connect.",
|
||||
"emailScopeName": "Correu electrònic",
|
||||
"emailScopeDescription": "Permet a l'app l'accés a la vostra adreça de correu electrònic.",
|
||||
"profileScopeName": "Perfil",
|
||||
"profileScopeDescription": "Permet a l'app l'accés a la vostra informació de perfil.",
|
||||
"groupsScopeName": "Grups",
|
||||
"groupsScopeDescription": "Permet a l'app l'accés la vostra informació de grup.",
|
||||
"backToLoginButton": "Tornar a l'inici de sessió",
|
||||
"phoneScopeName": "Telèfon",
|
||||
"phoneScopeDescription": "Permet a l'app l'accés al vostre número de telèfon.",
|
||||
"addressScopeName": "Adreça",
|
||||
"addressScopeDescription": "Permet a l'app l'accés a la vostra adreça.",
|
||||
"loginTailscaleTitle": "Continuar amb Tailscale",
|
||||
"loginTailscaleDescription": "Sembla que esteu accedint a Tinyauth des d'un dispositiu Tailscale autoritzat. Voldríeu continuar amb la vostra connexió Tailscale?",
|
||||
"loginTailscaleDeviceName": "Nom del dispositiu:",
|
||||
"loginTailscaleOtherMethod": "Iniciar la sessió amb un altre mètode",
|
||||
"loginTailscaleSuccess": "Autenticat correctament amb Tailscale.",
|
||||
"loginTailscaleFail": "Errada en autentificar amb Tailscale. Si us plau, intenteu-ho una altra vegada o feu servir un altre mètode d'autenticació.",
|
||||
"logoutTailscaleSubtitle": "Heu iniciat sessió actualment amb Tailscale en el vostre dispositiu <code>{{deviceName}}</code>. Cliqueu el botó de sota per a tancar la sessió.",
|
||||
"quickActionsLanguage": "Idioma",
|
||||
"quickActionsTheme": "Tema",
|
||||
"quickActionsThemeLight": "Clar",
|
||||
"quickActionsThemeDark": "Fosc",
|
||||
"quickActionsThemeSystem": "Sistema",
|
||||
"quickActionsLogout": "Sortir de la sessió",
|
||||
"quickActionsTitle": "Accions ràpides",
|
||||
"quickActionsProviderLocal": "Local",
|
||||
"quickActionsProviderLDAP": "LDAP",
|
||||
"quickActionsProviderOAuth": "{{provider}} OAuth"
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/cenkalti/backoff/v5 v5.0.3
|
||||
github.com/docker/docker v28.5.2+incompatible
|
||||
github.com/gin-gonic/gin v1.12.0
|
||||
github.com/go-jose/go-jose/v4 v4.1.4
|
||||
github.com/go-jose/go-jose/v4 v4.1.5
|
||||
github.com/go-ldap/ldap/v3 v3.4.14
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
@@ -23,14 +23,14 @@ require (
|
||||
github.com/tinyauthapp/paerser v0.0.0-20260410140347-85c3740d6298
|
||||
github.com/weppos/publicsuffix-go v0.50.3
|
||||
go.uber.org/dig v1.19.0
|
||||
golang.org/x/crypto v0.55.0
|
||||
golang.org/x/crypto v0.56.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/oauth2 v0.36.0
|
||||
golang.org/x/tools v0.49.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/apimachinery v0.37.0
|
||||
k8s.io/client-go v0.37.0
|
||||
modernc.org/sqlite v1.57.0
|
||||
modernc.org/sqlite v1.58.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -145,9 +145,9 @@ require (
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad // indirect
|
||||
k8s.io/utils v0.0.0-20260626114624-be93311217bd // indirect
|
||||
modernc.org/libc v1.74.4 // indirect
|
||||
modernc.org/libc v1.75.6 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/memory v1.12.1 // indirect
|
||||
rsc.io/qr v0.2.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
|
||||
@@ -115,8 +115,8 @@ github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
|
||||
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8 h1:H9AZkK22UOmfX8J84ubyaZxKJZ3FMHVwn8swoMML7iQ=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-jose/go-jose/v4 v4.1.5 h1:RjgjO2LOtWOJKUC5wpwY9LR3B3vwVAz6JS2YHfYU6eA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.5/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-ldap/ldap/v3 v3.4.14 h1:D6PYdEgsaVzsXyr6w/yDC06Ria4uUhWm+Rb+er8lfAs=
|
||||
github.com/go-ldap/ldap/v3 v3.4.14/go.mod h1:S4eJUMUNjDkE0ZJtIZdybwyb03sGGLW6gxXT1Hs8VKA=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
@@ -355,8 +355,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
|
||||
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y=
|
||||
golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
|
||||
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
|
||||
golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74=
|
||||
@@ -410,30 +410,30 @@ k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3d
|
||||
k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I=
|
||||
k8s.io/utils v0.0.0-20260626114624-be93311217bd h1:Ea7fgQ5we8Y9T0OX5o0dAHzQOBRI07D/dEYRaB9ZZEs=
|
||||
k8s.io/utils v0.0.0-20260626114624-be93311217bd/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
|
||||
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
|
||||
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
|
||||
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k=
|
||||
modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co=
|
||||
modernc.org/libc v1.75.6 h1:yKk8qo+Di4gkmvRboK8ocCqH22FiUCR6jRy2OwtCRus=
|
||||
modernc.org/libc v1.75.6/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
|
||||
modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.57.0 h1:qNQP6xnx5M0ISNtlnxoOX0+cD5bJ0/gr9aMmndFczzg=
|
||||
modernc.org/sqlite v1.57.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ=
|
||||
modernc.org/sqlite v1.58.0 h1:38u40/bwkfM7f0Myhosl+SEMltSDxnGdQf8o6Kjmys0=
|
||||
modernc.org/sqlite v1.58.0/go.mod h1:rsD2CckafgObKC4DhBlGBf+RiHxkc3hINGt1Xw32tVY=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
{{ if eq .Proxy "caddy" }}
|
||||
{{ template "caddy" . }}
|
||||
{{ else if eq .Proxy "traefik" }}
|
||||
{{ template "traefik" . }}
|
||||
{{ else if eq .Proxy "nginx" }}
|
||||
{{ template "nginx" . }}
|
||||
{{ else if eq .Proxy "envoy" }}
|
||||
{{ template "envoy" . }}
|
||||
{{ else }}
|
||||
{{ template "traefik" . }}
|
||||
{{ end }}
|
||||
|
||||
whoami:
|
||||
image: traefik/whoami:v1.12.0
|
||||
pull_policy: missing
|
||||
|
||||
tinyauth:
|
||||
{{ if .Build }}
|
||||
build:
|
||||
context: ../
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- VERSION=e2e
|
||||
- BUILD_TAGS=nomsgpack
|
||||
- LDFLAGS=-s -w
|
||||
{{ else }}
|
||||
image: ghcr.io/tinyauthapp/tinyauth:{{ .Version }}
|
||||
{{ end }}
|
||||
pull_policy: missing
|
||||
command: ["--configfile", "/config.yaml"]
|
||||
volumes:
|
||||
- ./tinyauth.config.yaml:/config.yaml:ro
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{{ define "caddy" }}
|
||||
caddy:
|
||||
image: caddy:2.11.4
|
||||
pull_policy: missing
|
||||
ports:
|
||||
- {{ .Proxy.Port }}:80
|
||||
configs:
|
||||
- source: caddy_caddyfile
|
||||
target: /etc/caddy/Caddyfile
|
||||
mode: 0664
|
||||
{{ end }}
|
||||
|
||||
{{ define "caddy_config" }}
|
||||
caddy_caddyfile:
|
||||
content: |
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
{{ if .Services.Whoami.Enabled }}
|
||||
http://{{ .Domains.Whoami }} {
|
||||
forward_auth tinyauth:3000 {
|
||||
uri /api/auth/caddy
|
||||
copy_headers Remote-User Remote-Name Remote-Email Remote-Groups
|
||||
}
|
||||
reverse_proxy whoami:80
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
http://{{ .Domains.Tinyauth }} {
|
||||
reverse_proxy tinyauth:3000
|
||||
}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,137 @@
|
||||
{{ define "envoy" }}
|
||||
envoy:
|
||||
image: envoyproxy/envoy:v1.33-latest
|
||||
pull_policy: missing
|
||||
ports:
|
||||
- {{ .Proxy.Port }}:80
|
||||
configs:
|
||||
- source: envoy_config
|
||||
target: /etc/envoy/envoy.yaml
|
||||
mode: 0664
|
||||
{{ end }}
|
||||
|
||||
{{ define "envoy_config" }}
|
||||
envoy_config:
|
||||
content: |
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: http
|
||||
address:
|
||||
socket_address:
|
||||
address: 0.0.0.0
|
||||
port_value: 80
|
||||
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
stat_prefix: ingress_http
|
||||
use_remote_address: true
|
||||
|
||||
route_config:
|
||||
name: routes
|
||||
|
||||
virtual_hosts:
|
||||
{{ if .Services.Whoami.Enabled }}
|
||||
- name: whoami
|
||||
domains:
|
||||
- {{ .Domains.Whoami }}
|
||||
|
||||
request_headers_to_add:
|
||||
- header:
|
||||
key: x-real-ip
|
||||
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||
append_action: OVERWRITE_IF_EXISTS_OR_ADD
|
||||
|
||||
routes:
|
||||
- match:
|
||||
prefix: /
|
||||
route:
|
||||
cluster: whoami
|
||||
{{ end }}
|
||||
|
||||
- name: tinyauth
|
||||
domains:
|
||||
- {{ .Domains.Tinyauth }}
|
||||
|
||||
typed_per_filter_config:
|
||||
envoy.filters.http.ext_authz:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthzPerRoute
|
||||
disabled: true
|
||||
|
||||
routes:
|
||||
- match:
|
||||
prefix: /
|
||||
route:
|
||||
cluster: tinyauth
|
||||
|
||||
http_filters:
|
||||
- name: envoy.filters.http.ext_authz
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
|
||||
failure_mode_allow: false
|
||||
|
||||
http_service:
|
||||
server_uri:
|
||||
uri: http://tinyauth:3000
|
||||
cluster: tinyauth
|
||||
timeout: 5s
|
||||
|
||||
path_prefix: "/api/auth/envoy?path="
|
||||
|
||||
authorization_request:
|
||||
allowed_headers:
|
||||
patterns:
|
||||
- exact: cookie
|
||||
- exact: x-forwarded-for
|
||||
- exact: x-forwarded-proto
|
||||
- exact: x-real-ip
|
||||
- exact: user-agent
|
||||
|
||||
authorization_response:
|
||||
allowed_upstream_headers:
|
||||
patterns:
|
||||
- exact: remote-user
|
||||
- exact: remote-name
|
||||
- exact: remote-email
|
||||
- exact: remote-groups
|
||||
- exact: remote-sub
|
||||
- exact: authorization
|
||||
|
||||
allowed_client_headers:
|
||||
patterns:
|
||||
- exact: set-cookie
|
||||
- exact: content-type
|
||||
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
|
||||
clusters:
|
||||
{{ if .Services.Whoami.Enabled }}
|
||||
- name: whoami
|
||||
type: LOGICAL_DNS
|
||||
load_assignment:
|
||||
cluster_name: whoami
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: whoami
|
||||
port_value: 80
|
||||
{{ end }}
|
||||
|
||||
- name: tinyauth
|
||||
type: LOGICAL_DNS
|
||||
load_assignment:
|
||||
cluster_name: tinyauth
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: tinyauth
|
||||
port_value: 3000
|
||||
{{ end }}
|
||||
@@ -0,0 +1,79 @@
|
||||
{{ define "nginx" }}
|
||||
nginx:
|
||||
image: nginx:1.31.6
|
||||
pull_policy: missing
|
||||
ports:
|
||||
- {{ .Proxy.Port }}:80
|
||||
configs:
|
||||
- source: nginx_default
|
||||
target: /etc/nginx/conf.d/default.conf
|
||||
mode: 0664
|
||||
- source: nginx_tinyauth
|
||||
target: /etc/nginx/conf.d/tinyauth.conf
|
||||
mode: 0664
|
||||
{{ if .Services.Whoami.Enabled }}
|
||||
- source: nginx_whoami
|
||||
target: /etc/nginx/conf.d/whoami.conf
|
||||
mode: 0664
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "nginx_config" }}
|
||||
nginx_default:
|
||||
content: {}
|
||||
nginx_tinyauth:
|
||||
content: |
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ .Domains.Tinyauth }};
|
||||
|
||||
location / {
|
||||
proxy_pass http://tinyauth:3000;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
}
|
||||
}
|
||||
{{ if .Services.Whoami.Enabled }}
|
||||
nginx_whoami:
|
||||
content: |
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ .Domains.Whoami }};
|
||||
|
||||
location / {
|
||||
proxy_pass http://whoami:80;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
|
||||
auth_request /tinyauth;
|
||||
auth_request_set $redirection_url $upstream_http_x_tinyauth_location;
|
||||
error_page 401 403 =302 $redirection_url;
|
||||
}
|
||||
|
||||
location = /tinyauth {
|
||||
internal;
|
||||
|
||||
proxy_pass http://tinyauth:3000/api/auth/nginx;
|
||||
|
||||
proxy_pass_request_body off;
|
||||
proxy_set_header Content-Length "";
|
||||
|
||||
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
|
||||
proxy_set_header X-Original-Method $request_method;
|
||||
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Host $http_host;
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,27 @@
|
||||
package proxies
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// Bundle the templates
|
||||
|
||||
//go:embed caddy.yaml.tmpl
|
||||
var caddyTemplateSource string
|
||||
|
||||
//go:embed envoy.yaml.tmpl
|
||||
var envoyTemplateSource string
|
||||
|
||||
//go:embed nginx.yaml.tmpl
|
||||
var nginxTemplateSource string
|
||||
|
||||
//go:embed traefik.yaml.tmpl
|
||||
var traefikTemplateSource string
|
||||
|
||||
// Compile them and export them
|
||||
|
||||
var CaddyTemplate = template.Must(template.New("caddy").Parse(caddyTemplateSource))
|
||||
var EnvoyTemplate = template.Must(template.New("envoy").Parse(envoyTemplateSource))
|
||||
var NginxTemplate = template.Must(template.New("nginx").Parse(nginxTemplateSource))
|
||||
var TraefikTemplate = template.Must(template.New("traefik").Parse(traefikTemplateSource))
|
||||
@@ -0,0 +1,68 @@
|
||||
{{ define "traefik" }}
|
||||
traefik:
|
||||
image: traefik:v3.7.13
|
||||
pull_policy: missing
|
||||
ports:
|
||||
- {{ .Proxy.Port }}:80
|
||||
configs:
|
||||
- source: traefik_static
|
||||
target: /etc/traefik/traefik.yml
|
||||
mode: 0664
|
||||
- source: traefik_dynamic
|
||||
target: /etc/traefik/dynamic.yml
|
||||
mode: 0664
|
||||
{{ end }}
|
||||
|
||||
{{ define "traefik_config" }}
|
||||
traefik_static:
|
||||
content: |
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
|
||||
providers:
|
||||
file:
|
||||
filename: /etc/traefik/dynamic.yml
|
||||
traefik_dynamic:
|
||||
content: |
|
||||
http:
|
||||
routers:
|
||||
{{ if .Services.Whoami.Enabled }}
|
||||
whoami:
|
||||
rule: Host(`{{ .Domains.Whoami }}`)
|
||||
entryPoints:
|
||||
- web
|
||||
service: whoami
|
||||
middlewares:
|
||||
- tinyauth
|
||||
{{ end }}
|
||||
|
||||
tinyauth:
|
||||
rule: Host(`{{ .Domains.Tinyauth }}`)
|
||||
entryPoints:
|
||||
- web
|
||||
service: tinyauth
|
||||
|
||||
middlewares:
|
||||
tinyauth:
|
||||
forwardAuth:
|
||||
address: http://tinyauth:3000/api/auth/traefik
|
||||
authResponseHeaders:
|
||||
- Remote-User
|
||||
- Remote-Name
|
||||
- Remote-Email
|
||||
- Remote-Groups
|
||||
|
||||
services:
|
||||
{{ if .Services.Whoami.Enabled }}
|
||||
whoami:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://whoami:80
|
||||
{{ end }}
|
||||
|
||||
tinyauth:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://tinyauth:3000
|
||||
{{ end }}
|
||||
@@ -1,6 +1,8 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -545,7 +547,9 @@ func (controller *OIDCController) Token(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if client.ClientSecret != creds.ClientSecret {
|
||||
clientSecretHash := sha256.Sum256([]byte(client.ClientSecret))
|
||||
providedSecretHash := sha256.Sum256([]byte(creds.ClientSecret))
|
||||
if subtle.ConstantTimeCompare(clientSecretHash[:], providedSecretHash[:]) != 1 {
|
||||
controller.log.App.Warn().Str("clientId", creds.ClientID).Msg("Invalid client secret")
|
||||
c.JSON(400, gin.H{
|
||||
"error": "invalid_client",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -882,7 +883,7 @@ func (service *OIDCService) ValidatePKCE(codeChallenge string, codeVerifier stri
|
||||
if codeChallenge == "" {
|
||||
return true
|
||||
}
|
||||
return codeChallenge == service.hashAndEncodePKCE(codeVerifier)
|
||||
return subtle.ConstantTimeCompare([]byte(codeChallenge), []byte(service.hashAndEncodePKCE(codeVerifier))) == 1
|
||||
}
|
||||
|
||||
func (service *OIDCService) hashAndEncodePKCE(codeVerifier string) string {
|
||||
|
||||
Reference in New Issue
Block a user